SponsorsTable of ContentsGetting StartedInstallationMigration guide
Usage
CoerceLiteralsStringsNumbersBigIntsNaNsBooleansDatesEnumsOptionalsNullablesObjectsArraysUnionsRecordsMapsSetsIntersectionsRecursive typesPromisesInstanceofFunctionsPreprocess
Schema methods
Custom schemas
Guides and Concepts
Error HandlingComparisonEcosystem
Contributing
ChangelogCode of ConductLICENSE
Links
Clerk
⌘+J

© 2025 Zod


Designed in Earth-616

Build by oeri

Recursive types

You can define a recursive schema in Zod, but because of a limitation of TypeScript, their type can't be statically inferred. Instead you'll need to define the type definition manually, and provide it to Zod as a "type hint".

const baseCategorySchema = z.object({
  name: z.string()
});
 
type Category = z.infer<typeof baseCategorySchema> & {
  subcategories: Category[];
};
 
const categorySchema: z.ZodType<Category> = baseCategorySchema.extend({
  subcategories: z.lazy(() => categorySchema.array())
});
 
categorySchema.parse({
  name: "People",
  subcategories: [
    {
      name: "Politicians",
      subcategories: [
        {
          name: "Presidents",
          subcategories: []
        }
      ]
    }
  ]
}); // passes

Thanks to crasite for this example.

ZodType with ZodEffects

When using z.ZodType with z.ZodEffects ( .refine, .transform, preprocess, etc... ), you will need to define the input and output types of the schema. z.ZodType<Output, z.ZodTypeDef, Input>

const isValidId = (id: string): id is `${string}/${string}` => id.split("/").length === 2;
 
const baseSchema = z.object({
  id: z.string().refine(isValidId)
});
 
type Input = z.input<typeof baseSchema> & {
  children: Input[];
};
 
type Output = z.output<typeof baseSchema> & {
  children: Output[];
};
 
const schema: z.ZodType<Output, z.ZodTypeDef, Input> = baseSchema.extend({
  children: z.lazy(() => schema.array())
});

Thanks to marcus13371337 and JoelBeeldi for this example.

JSON type

If you want to validate any JSON value, you can use the snippet below.

const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;
type Json = Literal | { [key: string]: Json } | Json[];
const jsonSchema: z.ZodType<Json> = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]));
 
jsonSchema.parse(data);

Thanks to ggoodman for suggesting this.

Cyclical objects

Despite supporting recursive schemas, passing cyclical data into Zod will cause an infinite loop in some cases.

To detect cyclical objects before they cause problems, consider this approach.

On This Page

Recursive types
Zodtype with zodeffects
Json type
Cyclical objects

Edit this page on GitHub