Defining schemas
To validate data, you must first define a schema. Schemas represent types, from simple primitive values to complex nested objects and arrays.
Primitives
To coerce input data to the appropriate type, use z.coerce
instead:
The coerced variant of these schemas attempts to convert the input value to the appropriate type.
Literals
Literal schemas represent a literal type, like "hello world"
or 5
.
To represent the JavaScript literals null
and undefined
:
To allow multiple literal values:
To extract the set of allowed values from a literal schema:
Strings
Zod provides a handful of built-in string validation and transform APIs. To perform some common string validations:
To perform some simple string transforms:
String formats
To validate against some common string formats:
Emails
To validate email addresses:
By default, Zod uses a comparatively strict email regex designed to validate normal email addresses containing common characters. It's roughly equivalent to the rules enforced by Gmail. To learn more about this regex, refer to this post.
To customize the email validation behavior, you can pass a custom regular expression to the pattern
param.
Zod exports several useful regexes you could use.
UUIDs
To validate UUIDs:
To specify a particular UUID version:
The RFC 4122 UUID spec requires the first two bits of byte 8 to be 10
. Other UUID-like identifiers do not enforce this constraint. To validate any UUID-like identifier:
ISO datetimes
As you may have noticed, Zod string includes a few date/time related validations. These validations are regular expression based, so they are not as strict as a full date/time library. However, they are very convenient for validating user input.
The z.iso.datetime()
method enforces ISO 8601; by default, no timezone offsets are allowed:
To allow timesone offsets:
To allow unqualified (timezone-less) datetimes:
To constrain the allowable precision
(by default, arbitrary sub-second precision is supported).
ISO dates
The z.iso.date()
method validates strings in the format YYYY-MM-DD
.
ISO times
Added in Zod 3.23
The z.iso.time()
method validates strings in the format HH:MM:SS[.s+]
. The second can include arbitrary decimal precision. It does not allow timezone offsets of any kind.
You can set the precision
option to constrain the allowable decimal precision.
IP addresses
IP blocks (CIDR)
Validate IP address ranges specified with CIDR notation. By default, .cidr()
allows both IPv4 and IPv6.
Numbers
Use z.number()
to validate numbers. It allows any finite number.
Zod implements a handful of number-specific validations:
If (for some reason) you want to validate NaN
, use z.nan()
.
Integers
To validate integers:
BigInts
To validate BigInts:
Zod includes a handful of bigint-specific validations.
Booleans
To validate boolean values:
Dates
Use z.date()
to validate Date
instances.
To customize the error message:
Zod provides a handful of date-specific validations.
Enums
Use z.enum
to validate inputs against a fixed set of allowable string values.
Careful — If you declare your string array as a variable, Zod won't be able to properly infer the exact values of each element.
To fix this, always pass the array directly into the z.enum()
function, or use as const
.
You can also pass in an externally-declared TypeScript enum.
Zod 4 — This replaces the z.nativeEnum()
API in Zod 3.
Note that using TypeScript's enum
keyword is not recommended.
.enum
To extract the schema's values as an enum-like object:
.exclude()
To create a new enum schema, excluding certain values:
.extract()
To create a new enum schema, extracting certain values:
Stringbool
💎 New in Zod 4
In some cases (e.g. parsing environment variables) it's valuable to parse certain string "boolish" values to a plain boolean
value. To support this, Zod 4 introduces z.stringbool()
:
To customize the truthy and falsy values:
Be default the schema is case-insensitive; all inputs are converted to lowercase before comparison to the truthy
/falsy
values. To make it case-sensitive:
Optionals
To make a schema optional (that is, to allow undefined
inputs).
This returns a ZodOptional
instance that wraps the original schema. To extract the inner schema:
Nullables
To make a schema nullable (that is, to allow null
inputs).
This returns a ZodNullable
instance that wraps the original schema. To extract the inner schema:
Nullish
To make a schema nullish (both optional and nullable):
Refer to the TypeScript manual for more about the concept of nullish.
Unknown
Zod aims to mirror TypeScript's type system one-to-one. As such, Zod provides APIs to represent the following special types:
Never
No value will pass validation.
Template literals
💎 New in Zod 4
Zod 4 finally implements one of the last remaining unrepresented features of TypeScript's type system: template literals. Virtually all primitive schemas can be used in z.templateLiteral
: strings, string formats like z.email()
, numbers, booleans, enums, literals (of the non-template variety), optional/nullable, and other template literals.
Objects
There are two APIs for defining object schemas: z.object()
and z.interface()
. The two APIs are largely identical, with a small (but important!) difference in how optional properties are defined.
To define an object type:
By default, all properties are required. To make certain properties optional:
By default, unrecognized keys are stripped from the parsed result:
To define a strict schema that throws an error when unknown keys are found:
To define a loose schema that allows unknown keys to pass through:
.shape
To access the internal schemas:
.keyof()
To create a ZodEnum
schema from the keys of an object schema:
.extend()
To add additional fields to an object schema:
This API can be used to overwrite existing fields! Be careful with this power!
You can also pass another object schema into .extend()
:
If the two schemas share keys, B will override A. The returned schema also inherits the strictness level (strip, strict, loose) from B.
.pick
Inspired by TypeScript's built-in Pick
and Omit
utility types, Zod provides dedicated APIs for picking and omitting certain keys from an object schema.
Starting from this initial schema:
To pick certain keys:
.omit
To omit certain keys:
.partial()
For convenience, Zod provides a dedicated API for making some or all properties optional, inspired by the built-in TypeScript utility type Partial
.
To make all fields optional:
To make certain properties optional:
.required()
Zod provides an API for making some or all properties required, inspired by TypeScript's Required
utility type.
To make all properties required:
To make certain properties required:
Recursive objects
The z.interface()
API is capable of representing recursive types using getters.
You can also represent mutually recursive types:
All object APIs (pick, omit, required, partial, etc) work as you'd expect.
Though recursive schemas are supported, passing cyclical data into Zod will cause an infinite loop.
To detect cyclical objects before they cause problems, consider this approach.
Arrays
To define an array schema:
To access the inner schema for an element of the array.
.min/.max/.length
Tuples
Unlike arrays, tuples are typically fixed-length arrays that specify different schemas for each index.
To add a variadic ("rest") argument:
Unions
Union types (A | B
) represent a logical "OR". Zod union schemas will check the input against each option in order. The first value that validates successfully is returned.
To extract the internal option schemas:
Discriminated unions
A discriminated union is a special kind of union in which a) all the options are object schemas that b) share a particular key (the "discriminator"). Based on the value of the discriminator key, TypeScript is able to "narrow" the type signature as you'd expect.
You could represent with with a regular z.union()
. But regular unions are naive—they check the input against each option in order and return the first one that passes. This can be slow for large unions.
So Zod provides a z.discriminatedUnion()
API that uses a discriminator key to make parsing more efficient.
In Zod 3, you were required to specify the discriminator key as the first argument. This is no longer necessary, as Zod can now automatically detect the discriminator key.
If Zod can't find a discriminator key, it will throw an error at schema creation time.
Intersections
Intersection types (A & B
) represent a logical "AND".
This can be useful for intersecting two object types.
In most cases, it is better to use A.extend(B)
to merge two object schemas. This approach returns a new object schema, whereas z.intersection(A, B)
returns a ZodIntersection
instance which lacks common object methods like pick
and omit
.
Records
Record schemas are used to validate types such as Record<string, number>
.
The key schema can be any Zod schema that as assignable to string | number | symbol
.
To create an object schemas containing keys defined by an enum:
Maps
Sets
Set schemas can be further constrained with the following utility methods.
Promises
Deprecated — z.promise()
is deprecated in Zod 4. There are vanishingly few valid uses cases for a Promise
schema. If you suspect a value might be a Promise
, simply await
it before parsing it with Zod.
Instanceof
You can use z.instanceof
to check that the input is an instance of a class. This is useful to validate inputs against classes that are exported from third-party libraries.
Refinements
Every Zod schema stores an array of refinements. Refinements are a way to perform custom validation that can Zod doesn't provide a native API for.
.refine()
Refinement functions should never throw. Instead they should return a falsy value to signal failure. Thrown errors are not caught by Zod.
To customize the error message:
By default, validation issues from checks are considered continuable; that is, Zod will execute all checks in sequence, even if one of them causes a validation error. This is usually desirable, as it means Zod can surface as many errors as possible in one go.
To mark a particular refinement as non-continuable, use the abort
parameter. Validation will terminate if the check fails.
To customize the error path, use the path
parameter. This is typically only useful in the context of object schemas.
This will set the path
parameter in the associated issue:
Refinements can be async
:
If you use async refinements, you must use the .parseAsync
method to parse data! Otherwise Zod will throw an error.
.superRefine()
In Zod 4, .superRefine()
has been deprecated in favor of .check()
.check()
The .refine()
API is syntactic sugar atop a more versatile (and verbose) API called .check()
. You can use this API to create multiple issues in a single refinement or have full control of the generated issue objects.
The regular .refine
API only generates issues with a "custom"
error code, but .check()
makes it possible to throw other issue types. For more information on Zod's internal issue types, read the Error customization docs.
Pipes
Schemas can be chained together into "pipes". Pipes are primarily useful when used in conjunction with Transforms.
Transforms
Transforms are a special kind of schema. Instead of validating input, they accept anything and perform some transformation on the data. To define a transform:
To perform validation logic inside a transform, use ctx
. To report a validation issue, push a new issue onto ctx.issues
(similar to the .check()
API).
Most commonly, transforms are used in conjunction with Pipes. This combination is useful for performing some initial validation, then transforming the parsed data into another form.
.transform()
Piping some schema into a transform is a common pattern, so Zod provides a convenience .transform()
method.
Transforms can also be async:
If you use async transforms, you must use a .parseAsync
or .safeParseAsync
when parsing data! Otherwise Zod will throw an error.
.preprocess()
Piping a transform into another schema is another common pattern, so Zod provides a convenience z.preprocess()
function.
Defaults
To set a default value for a schema:
Alternatively, you can pass a function which will be re-executed whenever a default value needs to be generated:
Catch
Use .catch()
to define a fallback value to be returned in the event of a validation error:
Alternatively, you can pass a function which will be re-executed whenever a catch value needs to be generated.
Branded types
TypeScript's type system is structural, meaning that two types that are structurally equivalent are considered the same.
In some cases, it can be desirable to simulate nominal typing inside TypeScript. This can be achieved with branded types (also known as "opaque types").
Under the hood, this works by attaching a "brand" to the schema's inferred type.
With this brand, any plain (unbranded) data structures are no longer assignable to the inferred type. You have to parse some data with the schema to get branded data.
Note that branded types do not affect the runtime result of .parse
. It is a static-only construct.
Readonly
To mark a schema as readonly:
This returns a new schema that wraps the original. The new schema's inferred type will be marked as readonly
. Note that this only affects objects, arrays, tuples, Set
, and Map
in TypeScript:
Inputs will be parsed using the original schema, then the result will be frozen with Object.freeze()
to prevent modifications.
Template literals
New in Zod 4
To define a template literal schema:
The z.templateLiteral
API can handle any number of string literals (e.g. "hello"
) and schemas. Any schema with an inferred type that's assignable to string | number | bigint | boolean | null | undefined
can be passed.
JSON
To validate any JSON-encodable value:
This is a convenience API that returns the following union schema:
Custom
You can create a Zod schema for any TypeScript type by using z.custom()
. This is useful for creating schemas for types that are not supported by Zod out of the box, such as template string literals.
If you don't provide a validation function, Zod will allow any value. This can be dangerous!
You can customize the error message and other options by passing a second argument. This parameter works the same way as the params parameter of .refine
.