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
Coercion
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:
URLs
To validate any WHATWG-compatible URL.
Internally this uses the new URL()
constructor to perform validation. This may behave differently across platforms and runtimes but is generally the most rigorous way to validate URIs/URLs.
To validate the hostname against a specific regex:
To validate the protocol against a specific regex, use the protocol
param.
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 timezone 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.
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
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:
z.strictObject
To define a strict schema that throws an error when unknown keys are found:
z.looseObject
To define a loose schema that allows unknown keys to pass through:
.catchall()
To defina a catchall schema that will be used to validate any unrecognized keys:
.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!
If the two schemas share keys, B will override A.
.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
To define a self-referential type, use a getter on the key. This lets JavaScript resolve the cyclical schema at runtime.
Though recursive schemas are supported, passing cyclical data into Zod will cause an infinite loop.
You can also represent mutually recursive types:
All object APIs (.pick()
, .omit()
, .required()
, .partial()
, etc.) work as you'd expect.
Due to TypeScript limitations, recursive type inference can be finicky, and it only works in certain scenarios. Some more complicated types may trigger recursive type errors like this:
In these cases, you can resolve the error with a type annotation on the offending getter:
Arrays
To define an array schema:
To access the inner schema for an element of the array.
Zod implements a number of array-specific validations:
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 it 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.
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 is assignable to string | number | symbol
.
To create an object schemas containing keys defined by an enum:
Zod 4 — In Zod 4, if you pass a z.enum
as the first argument to z.record()
, Zod will exhaustively check that all enum values exist in the input as keys. This behavior agrees with TypeScript:
In Zod 3, exhaustiveness was not checked. To replicate the Zod 3 behavior, use z.partialRecord()
.
If you want a partial record type, use z.partialRecord()
. This skips the special exhaustiveness checks Zod normally runs with z.enum()
and z.literal()
key schemas.
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 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:
Prefaults
In Zod, setting a default value will short-circuit the parsing process. If the input is undefined
, the default value is eagerly returned. As such, the default value must be assignable to the output type of the schema.
Sometimes, it's useful to define a prefault ("pre-parse default") value. If the input is undefined
, the prefault value will be parsed instead. The parsing process is not short circuited. As such, the prefault value must be assignable to the input type of the schema.
This is also useful if you want to pass some input value through some mutating refinements.
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
.
Functions
In Zod 4, z.function()
no longer returns a Zod schema.
Zod provides a z.function()
utility for defining Zod-validated functions. This way, you can avoid intermixing validation code with your business logic.
Function schemas have an .implement()
method which accepts a function and returns a new function that automatically validates its inputs and outputs.
This function will throw a ZodError
if the input is invalid:
If you only care about validating inputs, omit the output
field is optional.