
Zod
- 57 installs
- 2 repo stars
- Updated August 3, 2026
- fandhe-ai/agent-reference-skills
Helps with ai & agent building tasks.
About
zod is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- zod
- AI & Agent Building
- AI-coding skill
Zod by the numbers
- 57 all-time installs (skills.sh)
- Ranked #6,669 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fandhe-ai/agent-reference-skills --skill zodAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 57 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 3, 2026 |
| Repository | fandhe-ai/agent-reference-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Zod リファレンス
Zod 公式ドキュメントの全 API を網羅したスキル。 ユーザーのタスクに応じて適切な README.md を読み、そこから個別ファイルへ辿ること。
ディレクトリ構成
skills/zod/
SKILL.md
references/
getting-started/
README.md
introduction.md
basic-usage.md
api/
README.md
primitives.md
strings.md
numbers.md
enums-and-literals.md
objects.md
collections.md
unions-and-intersections.md
special-types.md
transforms-and-refinements.md
errors/
README.md
error-customization.md
error-formatting.md
advanced/
README.md
metadata.md
json-schema.md
codecs.md
ecosystem/
README.md
ecosystem.md
library-authors.md
migration/
README.md
v4-release-notes.md
v4-migration-guide.md
packages/
README.md
zod.md
mini.md
core.md
samples/
README.md
basic-schema.md
safe-parse.md
string-validation.md
object-composition.md
transform-and-default.md
custom-refinement.md
discriminated-union.md
error-formatting.md
error-customization.md
recursive-schema.md
scripts/
README.md
install.md
imports.md
parse.md
json-schema.md
migrate-v3-to-v4.md探索手順
タスクからカテゴリを引き、カテゴリの README.md で目的のページを特定する:
1. 下記マッピング表でタスクに対応するカテゴリを探す 2. そのカテゴリの references/{category}/README.md を参照して目的のページを特定する 3. 該当ページの .md を Read して詳細を確認する
タスク → カテゴリ マッピング
| タスク | カテゴリ | 参照 README |
|---|---|---|
| インストール・基本的な使い方を知りたい | getting-started | references/getting-started/README.md |
| parse / safeParse / z.infer で型推論したい | getting-started | references/getting-started/README.md |
| z.string / z.number / z.boolean 等のプリミティブ型を使いたい | api | references/api/README.md |
| z.object / z.array / z.record / z.tuple を使いたい | api | references/api/README.md |
| z.union / z.discriminatedUnion / z.intersection を使いたい | api | references/api/README.md |
| refine / superRefine / transform / pipe / default を使いたい | api | references/api/README.md |
| ZodError のメッセージをカスタマイズ・フォーマットしたい | errors | references/errors/README.md |
| i18n・グローバルエラー設定を変更したい | errors | references/errors/README.md |
| JSON Schema への変換・OpenAPI 連携をしたい | advanced | references/advanced/README.md |
| レジストリ・メタデータ・コーデックを使いたい | advanced | references/advanced/README.md |
| サードパーティ連携・エコシステムを知りたい | ecosystem | references/ecosystem/README.md |
| Zod を使うライブラリを開発したい | ecosystem | references/ecosystem/README.md |
| Zod 3 から Zod 4 に移行したい | migration | references/migration/README.md |
| Zod 4 の新機能・破壊的変更を確認したい | migration | references/migration/README.md |
| Zod / Zod Mini / Zod Core の違いを知りたい | packages | references/packages/README.md |
| バンドルサイズを削減したい(tree-shaking) | packages | references/packages/README.md |
| 典型的な使い方のサンプルを見たい | samples | samples/README.md |
| インストール・import・CLI コマンドを知りたい | scripts | scripts/README.md |
Codecs
Bidirectional transformations with encode and decode for safe data serialization and deserialization.
Codec Fundamentals
Codecs are schemas that support bidirectional transformations:
- Forward (decode):
Input -> Output-- the standard parsing direction - Backward (encode):
Output -> Input-- the reverse direction
This makes codecs ideal for converting between serialized formats (strings, bytes) and rich runtime types (Date, URL, BigInt).
Type Safety: .parse() vs .decode() vs .encode()
import * as z from "zod";
const codec = z.iso.datetime({ offset: true }).pipe(z.date());
// .parse() — accepts Input, returns Output (same as always)
codec.parse("2024-01-01T00:00:00Z");
// => Date object
// .decode() — accepts Input, returns Output (same as .parse() for codecs)
codec.decode("2024-01-01T00:00:00Z");
// => Date object
// .encode() — accepts Output, returns Input (reverse direction)
codec.encode(new Date("2024-01-01T00:00:00Z"));
// => "2024-01-01T00:00:00.000Z"Async Variants
Codecs support async operations for schemas with async refinements or transforms:
// Safe decode (returns result object instead of throwing)
const result = codec.safeDecode(input);
// => { success: true, data: output } | { success: false, error: ZodError }
// Async decode
await codec.decodeAsync(input);
// Safe async decode
await codec.safeDecodeAsync(input);
// Async encode
await codec.encodeAsync(output);Encoding Mechanics
Pipes
When encoding, pipes are traversed in reverse order. The output schema validates first, then the transform is reversed:
const myCodec = z
.string()
.transform((val) => val.length)
.pipe(z.number());
myCodec.decode("hello"); // => 5
// forward: string -> transform(length) -> number
myCodec.encode(5);
// reverse: number -> (reverse transform not possible for arbitrary transforms)Refinements
Refinements are applied during encoding. The schema validates the output value, then the encoded input value is validated against input refinements.
Two-Pass Validation
Encoding performs two-pass validation: 1. Validate the output value against the output schema 2. Run the reverse transform 3. Validate the resulting input value against the input schema
Mutating Transforms
Transforms that lose information (like .transform(val => val.length)) cannot be reversed. Only use codecs with transforms that have a clear inverse.
Special Cases
Defaults and Prefaults (Forward-Only)
z.default() and z.prefault() only apply in the forward (decode) direction. During encoding, they are skipped:
const schema = z.string().default("fallback");
schema.decode(undefined); // => "fallback"
schema.encode("hello"); // => "hello"Catch (Forward-Only)
z.catch() only applies in the forward direction. During encoding, catch handlers are not invoked:
const schema = z.string().catch("fallback");
schema.decode(123); // => "fallback"
schema.encode("hello"); // => "hello"z.stringbool()
z.stringbool() is a built-in codec that converts "true"/"false" strings to booleans:
const schema = z.stringbool();
schema.decode("true"); // => true
schema.decode("false"); // => false
schema.encode(true); // => "true"
schema.encode(false); // => "false"Unidirectional Transforms
Schemas using .transform() without .pipe() are unidirectional -- they support .parse() and .decode() but not .encode(). Calling .encode() on a unidirectional transform will throw an error.
Built-in Codecs
Zod provides 16 built-in codecs for common conversions:
String to Number
z.stringToNumber();
// decode: "42" -> 42
// encode: 42 -> "42"
z.stringToInt();
// decode: "42" -> 42 (must be integer)
// encode: 42 -> "42"String to BigInt
z.stringToBigInt();
// decode: "9007199254740993" -> 9007199254740993n
// encode: 9007199254740993n -> "9007199254740993"Number to BigInt
z.numberToBigInt();
// decode: 42 -> 42n
// encode: 42n -> 42Date Codecs
z.isoDatetimeToDate();
// decode: "2024-01-01T00:00:00Z" -> Date
// encode: Date -> "2024-01-01T00:00:00.000Z"
z.epochSecondsToDate();
// decode: 1704067200 -> Date
// encode: Date -> 1704067200
z.epochMillisToDate();
// decode: 1704067200000 -> Date
// encode: Date -> 1704067200000JSON Codec
z.json();
// decode: '{"key":"value"}' -> { key: "value" }
// encode: { key: "value" } -> '{"key":"value"}'Byte Codecs
z.utf8ToBytes();
// decode: "hello" -> Uint8Array
// encode: Uint8Array -> "hello"
z.bytesToUtf8();
// decode: Uint8Array -> "hello"
// encode: "hello" -> Uint8Array
z.base64ToBytes();
// decode: "aGVsbG8=" -> Uint8Array
// encode: Uint8Array -> "aGVsbG8="
z.base64urlToBytes();
// decode: "aGVsbG8" -> Uint8Array (URL-safe base64)
// encode: Uint8Array -> "aGVsbG8"
z.hexToBytes();
// decode: "68656c6c6f" -> Uint8Array
// encode: Uint8Array -> "68656c6c6f"URL Codecs
z.stringToURL();
// decode: "https://example.com" -> URL
// encode: URL -> "https://example.com"
z.stringToHttpURL();
// decode: "https://example.com" -> URL (only http/https)
// encode: URL -> "https://example.com"URI Component Codec
z.uriComponent();
// decode: "hello%20world" -> "hello world"
// encode: "hello world" -> "hello%20world"Related
- Metadata and Registries
- JSON Schema
- Defining Schemas
JSON Schema
Convert between Zod schemas and JSON Schema for OpenAPI definitions, AI structured outputs, and interoperability.
z.fromJSONSchema()
Experimental -- This function is experimental and may undergo changes in future releases.
Convert a JSON Schema into a Zod schema:
import * as z from "zod";
const jsonSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
required: ["name", "age"],
};
const zodSchema = z.fromJSONSchema(jsonSchema);z.toJSONSchema()
Convert a Zod schema to JSON Schema:
import * as z from "zod";
const schema = z.object({
name: z.string(),
age: z.number(),
});
z.toJSONSchema(schema);
// => {
// type: 'object',
// properties: { name: { type: 'string' }, age: { type: 'number' } },
// required: [ 'name', 'age' ],
// additionalProperties: false,
// }Parameters
A second argument customizes the conversion logic:
interface ToJSONSchemaParams {
/** JSON Schema version to target.
* - "draft-2020-12" — Default
* - "draft-07" — JSON Schema Draft 7
* - "draft-04" — JSON Schema Draft 4
* - "openapi-3.0" — OpenAPI 3.0 Schema Object */
target?:
| "draft-04"
| "draft-4"
| "draft-07"
| "draft-7"
| "draft-2020-12"
| "openapi-3.0"
| ({} & string)
| undefined;
/** A registry used to look up metadata for each schema.
* Any schema with an `id` property will be extracted as a $def. */
metadata?: $ZodRegistry<Record<string, any>>;
/** How to handle unrepresentable types.
* - "throw" — Default. Throws an error
* - "any" — Becomes {} */
unrepresentable?: "throw" | "any";
/** How to handle cycles.
* - "ref" — Default. Cycles broken using $defs
* - "throw" — Throws an error */
cycles?: "ref" | "throw";
/** How to handle reused schemas.
* - "inline" — Default. Reused schemas are inlined
* - "ref" — Extracted as $defs */
reused?: "ref" | "inline";
/** Convert `id` values to URIs for external $refs.
* Default is (id) => id. */
uri?: (id: string) => string;
}io
Some schema types have different input and output types (e.g. ZodPipe, ZodDefault, coerced primitives). By default, z.toJSONSchema represents the output type; use "io": "input" to extract the input type instead.
const mySchema = z.string().transform((val) => val.length).pipe(z.number());
const jsonSchema = z.toJSONSchema(mySchema);
// => { type: "number" }
const jsonSchema = z.toJSONSchema(mySchema, { io: "input" });
// => { type: "string" }target
Set the target JSON Schema version. Default is Draft 2020-12.
z.toJSONSchema(schema, { target: "draft-07" });
z.toJSONSchema(schema, { target: "draft-2020-12" });
z.toJSONSchema(schema, { target: "draft-04" });
z.toJSONSchema(schema, { target: "openapi-3.0" });metadata
Metadata stored in registries is included in the output. The .meta() method registers metadata in z.globalRegistry:
import * as z from "zod";
const emailSchema = z.string().meta({
title: "Email address",
description: "Your email address",
});
z.toJSONSchema(emailSchema);
// => { type: "string", title: "Email address", description: "Your email address", ... }All metadata fields get copied into the resulting JSON Schema:
const schema = z.string().meta({
whatever: 1234,
});
z.toJSONSchema(schema);
// => { type: "string", whatever: 1234 }unrepresentable
The following types have no JSON Schema equivalent and will throw by default:
z.bigint(); // not representable
z.int64(); // not representable
z.symbol(); // not representable
z.undefined(); // not representable
z.void(); // not representable
z.date(); // not representable
z.map(); // not representable
z.set(); // not representable
z.transform(); // not representable
z.nan(); // not representable
z.custom(); // not representablez.toJSONSchema(z.bigint());
// => throws Error
z.toJSONSchema(z.bigint(), { unrepresentable: "any" });
// => {} (equivalent of unknown in JSON Schema)cycles
Cycles are represented using $ref:
const User = z.object({
name: z.string(),
get friend() {
return User;
},
});
z.toJSONSchema(User);
// => {
// type: 'object',
// properties: { name: { type: 'string' }, friend: { '$ref': '#' } },
// required: [ 'name', 'friend' ],
// additionalProperties: false,
// }
z.toJSONSchema(User, { cycles: "throw" });
// => throws Errorreused
Control how schemas that occur multiple times are handled:
const name = z.string();
const User = z.object({
firstName: name,
lastName: name,
});
// Default: "inline"
z.toJSONSchema(User);
// => {
// type: 'object',
// properties: {
// firstName: { type: 'string' },
// lastName: { type: 'string' }
// },
// required: [ 'firstName', 'lastName' ],
// additionalProperties: false,
// }
// Extract to $defs
z.toJSONSchema(User, { reused: "ref" });
// => {
// type: 'object',
// properties: {
// firstName: { '$ref': '#/$defs/__schema0' },
// lastName: { '$ref': '#/$defs/__schema0' }
// },
// required: [ 'firstName', 'lastName' ],
// additionalProperties: false,
// '$defs': { __schema0: { type: 'string' } }
// }override
Define custom conversion logic. The callback receives the original Zod schema and the default JSON Schema. Directly modify `ctx.jsonSchema`:
const mySchema = /* ... */;
z.toJSONSchema(mySchema, {
override: (ctx) => {
ctx.zodSchema; // the original Zod schema
ctx.jsonSchema; // the default JSON Schema
// directly modify
ctx.jsonSchema.whatever = "sup";
},
});To handle unrepresentable types with override, set unrepresentable: "any" alongside it:
// support z.date() as ISO datetime strings
const result = z.toJSONSchema(z.date(), {
unrepresentable: "any",
override: (ctx) => {
const def = ctx.zodSchema._zod.def;
if (def.type === "date") {
ctx.jsonSchema.type = "string";
ctx.jsonSchema.format = "date-time";
}
},
});Conversion Details
String Formats
// Supported via `format`
z.email(); // => { type: "string", format: "email" }
z.iso.datetime(); // => { type: "string", format: "date-time" }
z.iso.date(); // => { type: "string", format: "date" }
z.iso.time(); // => { type: "string", format: "time" }
z.iso.duration(); // => { type: "string", format: "duration" }
z.ipv4(); // => { type: "string", format: "ipv4" }
z.ipv6(); // => { type: "string", format: "ipv6" }
z.uuid(); // => { type: "string", format: "uuid" }
z.guid(); // => { type: "string", format: "uuid" }
z.url(); // => { type: "string", format: "uri" }
// Supported via `contentEncoding`
z.base64(); // => { type: "string", contentEncoding: "base64" }
// Supported via `pattern`
z.base64url();
z.cuid();
z.emoji();
z.nanoid();
z.cuid2();
z.ulid();
z.cidrv4();
z.cidrv6();
z.mac();Numeric Types
// number
z.number(); // => { type: "number" }
z.float32(); // => { type: "number", exclusiveMinimum: ..., exclusiveMaximum: ... }
z.float64(); // => { type: "number", exclusiveMinimum: ..., exclusiveMaximum: ... }
// integer
z.int(); // => { type: "integer" }
z.int32(); // => { type: "integer", exclusiveMinimum: ..., exclusiveMaximum: ... }Object Schemas
By default, z.object() schemas include additionalProperties: false (matching Zod's default stripping behavior):
z.toJSONSchema(z.object({ name: z.string() }));
// => { type: 'object', properties: {...}, required: [...], additionalProperties: false }
// In "input" mode, additionalProperties is not set
z.toJSONSchema(z.object({ name: z.string() }), { io: "input" });
// => { type: 'object', properties: {...}, required: [...] }z.looseObject()-- never setsadditionalProperties: falsez.strictObject()-- always setsadditionalProperties: false
File Schemas
z.file();
// => { type: "string", format: "binary", contentEncoding: "binary" }
z.file().min(1).max(1024 * 1024).mime("image/png");
// => {
// type: "string",
// format: "binary",
// contentEncoding: "binary",
// contentMediaType: "image/png",
// minLength: 1,
// maxLength: 1048576,
// }Nullability
z.null();
// => { type: "null" }
z.nullable(z.string());
// => { oneOf: [{ type: "string" }, { type: "null" }] }
z.optional(z.string());
// => { type: "string" }Note: z.undefined() is unrepresentable in JSON Schema.
Registries with External $ref
Pass a registry into z.toJSONSchema() to generate multiple interlinked JSON Schemas. All schemas must have a registered id property.
import * as z from "zod";
const User = z.object({
name: z.string(),
get posts() {
return z.array(Post);
},
});
const Post = z.object({
title: z.string(),
content: z.string(),
get author() {
return User;
},
});
z.globalRegistry.add(User, { id: "User" });
z.globalRegistry.add(Post, { id: "Post" });
z.toJSONSchema(z.globalRegistry);
// => {
// schemas: {
// User: {
// id: 'User',
// type: 'object',
// properties: {
// name: { type: 'string' },
// posts: { type: 'array', items: { '$ref': 'Post' } }
// },
// ...
// },
// Post: {
// id: 'Post',
// type: 'object',
// properties: {
// title: { type: 'string' },
// content: { type: 'string' },
// author: { '$ref': 'User' }
// },
// ...
// }
// }
// }Use the uri option to produce fully-qualified $ref URIs:
z.toJSONSchema(z.globalRegistry, {
uri: (id) => `https://example.com/${id}.json`,
});
// $ref values become e.g. 'https://example.com/Post.json'Related
- Metadata and Registries
- Codecs
- Defining Schemas
Metadata and Registries
Associate schemas with strongly-typed metadata for documentation, code generation, AI structured outputs, and form validation.
Registries
Registries are collections of schemas, each associated with strongly-typed metadata.
Creating a Registry
import * as z from "zod";
const myRegistry = z.registry<{ description: string }>();Registry Methods
const mySchema = z.string();
myRegistry.add(mySchema, { description: "A cool schema!" });
myRegistry.has(mySchema); // => true
myRegistry.get(mySchema); // => { description: "A cool schema!" }
myRegistry.remove(mySchema);
myRegistry.clear(); // wipe registryTypeScript enforces that the metadata for each schema matches the registry's metadata type:
myRegistry.add(mySchema, { description: "A cool schema!" }); // ok
myRegistry.add(mySchema, { description: 123 }); // errorSpecial Handling for id
Zod registries treat the id property specially. An Error will be thrown if multiple schemas are registered with the same id value. This is true for all registries, including the global registry.
.register() Method
The .register() method adds a schema to a registry inline. Unlike other Zod methods, .register() returns the original schema (not a new instance).
const mySchema = z.string();
mySchema.register(myRegistry, { description: "A cool schema!" });
// => mySchema (same instance)This lets you define metadata inline in your schemas:
const mySchema = z.object({
name: z.string().register(myRegistry, { description: "The user's name" }),
age: z.number().register(myRegistry, { description: "The user's age" }),
});Generic Collections
If a registry is defined without a metadata type, you can use it as a generic "collection" with no metadata required:
const myRegistry = z.registry();
myRegistry.add(z.string());
myRegistry.add(z.number());z.globalRegistry
Zod provides a global registry that can be used to store metadata for JSON Schema generation or other purposes. It accepts the GlobalMeta interface:
export interface GlobalMeta {
id?: string;
title?: string;
description?: string;
deprecated?: boolean;
[k: string]: unknown;
}Register metadata in z.globalRegistry:
import * as z from "zod";
const emailSchema = z.email().register(z.globalRegistry, {
id: "email_address",
title: "Email address",
description: "Your email address",
examples: ["first.last@example.com"],
});Extending GlobalMeta via Declaration Merging
To globally augment the GlobalMeta interface, use declaration merging. Creating a zod.d.ts file in your project root is a common convention:
// zod.d.ts
declare module "zod" {
interface GlobalMeta {
// add new fields here
examples?: unknown[];
}
}
// forces TypeScript to consider the file a module
export {};.meta() Method
The .meta() method is a convenience method for registering a schema in z.globalRegistry.
Setting Metadata
const emailSchema = z.email().meta({
id: "email_address",
title: "Email address",
description: "Please enter a valid email address",
});Getting Metadata
Calling .meta() without an argument retrieves the metadata for a schema:
emailSchema.meta();
// => { id: "email_address", title: "Email address", ... }Metadata is associated with a specific schema instance. Zod methods are immutable and always return a new instance:
const A = z.string().meta({ description: "A cool string" });
A.meta(); // => { description: "A cool string" }
const B = A.refine((_) => true);
B.meta(); // => undefined.describe() Shorthand
The .describe() method is a shorthand for registering a schema in z.globalRegistry with just a description field. It exists for compatibility with Zod 3, but .meta() is now the recommended approach.
const emailSchema = z.email();
emailSchema.describe("An email address");
// equivalent to
emailSchema.meta({ description: "An email address" });Custom Registries
Referencing Inferred Types
The metadata type can reference the inferred type of a schema using z.$output and z.$input:
import * as z from "zod";
type MyMeta = { examples: z.$output[] };
const myRegistry = z.registry<MyMeta>();
myRegistry.add(z.string(), { examples: ["hello", "world"] });
myRegistry.add(z.number(), { examples: [1, 2, 3] });z.$output references the schema's inferred output type (z.infer<typeof schema>). z.$input references the input type.
Constraining Schema Types
Pass a second generic to z.registry() to constrain which schema types can be added:
import * as z from "zod";
const myRegistry = z.registry<{ description: string }, z.ZodString>();
myRegistry.add(z.string(), { description: "A string" }); // ok
myRegistry.add(z.number(), { description: "A number" }); // error
// ^ 'ZodNumber' is not assignable to parameter of type 'ZodString'Related
- JSON Schema
- Codecs
- Defining Schemas
Advanced
| Name | Description | Path |
|---|---|---|
| Codecs | Bidirectional transformations with encode and decode for safe data serialization and deserialization. | codecs.md |
| JSON Schema | Convert between Zod schemas and JSON Schema for OpenAPI definitions, AI structured outputs, and interoperability. | json-schema.md |
| Metadata and Registries | Associate schemas with strongly-typed metadata for documentation, code generation, AI structured outputs, and form validation. | metadata.md |
Collections
Array, tuple, record, map, set, and file schemas in Zod.
z.array()
Define an array schema:
import { z } from "zod";
const stringArray = z.array(z.string()); // or z.string().array().unwrap()
Access the inner element schema:
stringArray.unwrap(); // => string schemaArray Validations
import { z } from "zod";
z.array(z.string()).min(5); // must contain 5 or more items
z.array(z.string()).max(5); // must contain 5 or fewer items
z.array(z.string()).length(5); // must contain exactly 5 itemsz.tuple()
Tuples are fixed-length arrays with different schemas for each index:
import { z } from "zod";
const MyTuple = z.tuple([
z.string(),
z.number(),
z.boolean()
]);
type MyTuple = z.infer<typeof MyTuple>;
// [string, number, boolean]Variadic (Rest) Arguments
Add a variadic rest argument as the second parameter:
import { z } from "zod";
const variadicTuple = z.tuple([z.string()], z.number());
// => [string, ...number[]]z.record()
Record schemas validate types such as Record<string, string>:
import { z } from "zod";
const IdCache = z.record(z.string(), z.string());
type IdCache = z.infer<typeof IdCache>; // Record<string, string>
IdCache.parse({
carlotta: "77d2586b-9e8e-4ecf-8b21-ea7e0530eadd",
jimmie: "77d2586b-9e8e-4ecf-8b21-ea7e0530eadd",
});The key schema can be any Zod schema assignable to string | number | symbol:
import { z } from "zod";
const Keys = z.union([z.string(), z.number(), z.symbol()]);
const AnyObject = z.record(Keys, z.unknown());
// Record<string | number | symbol, unknown>Records with Enum Keys
Create objects with keys defined by an enum. Zod exhaustively checks that all enum values exist as keys:
import { z } from "zod";
const Keys = z.enum(["id", "name", "email"]);
const Person = z.record(Keys, z.string());
// { id: string; name: string; email: string }Numeric Keys
As of v4.2, Zod properly supports numeric keys. A number schema as a record key validates that the key is a valid numeric string:
import { z } from "zod";
const numberKeys = z.record(z.number(), z.string());
numberKeys.parse({
1: "one", // passes
2: "two", // passes
"1.5": "one", // passes
"-3": "two", // passes
abc: "one" // fails
});
// further validation is also supported
const intKeys = z.record(z.int().step(1).min(0).max(10), z.string());
intKeys.parse({
0: "zero", // passes
12: "twelve", // fails
});z.partialRecord()
Use for partial record types. Skips the exhaustiveness checks that z.record() runs with z.enum() and z.literal() key schemas:
import { z } from "zod";
const Keys = z.enum(["id", "name", "email"]);
const Person = z.partialRecord(Keys, z.string());
// { id?: string; name?: string; email?: string }Note: In Zod 3,z.record()did not check exhaustiveness.z.partialRecord()replicates the old Zod 3 behavior.
z.looseRecord()
By default, z.record() errors on keys that do not match the key schema. Use z.looseRecord() to pass through non-matching keys unchanged:
import { z } from "zod";
const schema = z
.object({ name: z.string() })
.and(z.looseRecord(z.string().regex(/_phone$/), z.e164()));
type schema = z.infer<typeof schema>;
// => { name: string } & Record<string, string>
schema.parse({
name: "John",
home_phone: "+12345678900", // validated as phone number
work_phone: "+12345678900", // validated as phone number
});z.map()
Validate Map instances:
import { z } from "zod";
const StringNumberMap = z.map(z.string(), z.number());
type StringNumberMap = z.infer<typeof StringNumberMap>; // Map<string, number>
const myMap = new Map();
myMap.set("one", 1);
myMap.set("two", 2);
StringNumberMap.parse(myMap);z.set()
Validate Set instances:
import { z } from "zod";
const NumberSet = z.set(z.number());
type NumberSet = z.infer<typeof NumberSet>; // Set<number>
const mySet = new Set();
mySet.add(1);
mySet.add(2);
NumberSet.parse(mySet);Set Validations
import { z } from "zod";
z.set(z.string()).min(5); // must contain 5 or more items
z.set(z.string()).max(5); // must contain 5 or fewer items
z.set(z.string()).size(5); // must contain exactly 5 itemsz.file()
Validate File instances:
import { z } from "zod";
const fileSchema = z.file();
fileSchema.min(10_000); // minimum .size (bytes)
fileSchema.max(1_000_000); // maximum .size (bytes)
fileSchema.mime("image/png"); // single MIME type
fileSchema.mime(["image/png", "image/jpeg"]); // multiple MIME typesNotes
z.array()supports both function and method syntax:z.array(z.string())orz.string().array()z.record()with enum keys is exhaustive in Zod 4 (all keys must be present) -- usez.partialRecord()for non-exhaustive behaviorz.looseRecord()is useful for modeling multiple pattern properties when combined with intersectionsz.set()andz.file()validation methods use.min(),.max(), and.size()/.mime()respectively- As of v4.2,
z.record()properly handles numeric keys with additional validation support
Related
- Objects
- Unions and Intersections
- Transforms and Refinements
Enums and Literals
Enum schemas, boolean validation, and string-to-boolean coercion in Zod.
z.enum()
Use z.enum() to validate inputs against a fixed set of allowable string values:
import { z } from "zod";
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
FishEnum.parse("Salmon"); // => "Salmon"
FishEnum.parse("Swordfish"); // throws ZodErrorUsing as const
If you declare the string array as a variable, you must use as const for Zod to properly infer the exact values:
import { z } from "zod";
// Without `as const` -- inferred type is just `string`
const fish = ["Salmon", "Tuna", "Trout"];
const BadEnum = z.enum(fish);
type BadEnum = z.infer<typeof BadEnum>; // string
// With `as const` -- inferred type is the union
const fishConst = ["Salmon", "Tuna", "Trout"] as const;
const GoodEnum = z.enum(fishConst);
type GoodEnum = z.infer<typeof GoodEnum>; // "Salmon" | "Tuna" | "Trout"Enum-Like Object Literals
Enum-like object literals ({ [key: string]: string | number }) are supported:
import { z } from "zod";
const Fish = {
Salmon: 0,
Tuna: 1
} as const;
const FishEnum = z.enum(Fish);
FishEnum.parse(Fish.Salmon); // passes
FishEnum.parse(0); // passes
FishEnum.parse(2); // failsTypeScript Enums
You can pass an externally-declared TypeScript enum:
import { z } from "zod";
enum Fish {
Salmon = 0,
Tuna = 1
}
const FishEnum = z.enum(Fish);
FishEnum.parse(Fish.Salmon); // passes
FishEnum.parse(0); // passes
FishEnum.parse(2); // failsNote: In Zod 4,z.enum()replaces the oldz.nativeEnum()API from Zod 3. Using TypeScript'senumkeyword is generally not recommended.
String enums also work:
enum Fish {
Salmon = "Salmon",
Tuna = "Tuna",
Trout = "Trout",
}
const FishEnum = z.enum(Fish);.enum Accessor
To extract the schema's values as an enum-like object:
import { z } from "zod";
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
FishEnum.enum;
// => { Salmon: "Salmon", Tuna: "Tuna", Trout: "Trout" }.exclude()
Create a new enum schema excluding certain values:
import { z } from "zod";
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]);Note: .exclude() is available in Zod but not in Zod Mini..extract()
Create a new enum schema extracting only certain values:
import { z } from "zod";
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
const SalmonAndTroutOnly = FishEnum.extract(["Salmon", "Trout"]);Note: .extract() is available in Zod but not in Zod Mini.z.stringbool()
New in Zod 4. Parse string "boolish" values to a plain boolean value. Useful for parsing environment variables:
import { z } from "zod";
const strbool = z.stringbool();
strbool.parse("true"); // => true
strbool.parse("1"); // => true
strbool.parse("yes"); // => true
strbool.parse("on"); // => true
strbool.parse("y"); // => true
strbool.parse("enabled"); // => true
strbool.parse("false"); // => false
strbool.parse("0"); // => false
strbool.parse("no"); // => false
strbool.parse("off"); // => false
strbool.parse("n"); // => false
strbool.parse("disabled"); // => false
strbool.parse("anything else"); // throws ZodErrorCustom Truthy/Falsy Values
import { z } from "zod";
// these are the defaults
z.stringbool({
truthy: ["true", "1", "yes", "on", "y", "enabled"],
falsy: ["false", "0", "no", "off", "n", "disabled"],
});Case Sensitivity
By default, the schema is case-insensitive (all inputs are lowercased before comparison). To make it case-sensitive:
import { z } from "zod";
z.stringbool({
case: "sensitive"
});z.boolean()
To validate boolean values:
import { z } from "zod";
z.boolean().parse(true); // => true
z.boolean().parse(false); // => falseNotes
- Always pass arrays directly to
z.enum()or useas constto preserve literal types z.enum()in Zod 4 replaces bothz.enum()andz.nativeEnum()from Zod 3.exclude()and.extract()are only available in Zod (not Zod Mini)z.stringbool()is case-insensitive by defaultz.stringbool()is new in Zod 4 and is a common solution for parsing environment variables- For JavaScript truthy/falsy coercion, use
z.coerce.boolean()instead
Related
- Primitives
- Unions and Intersections
- Objects
Numbers
Number, integer, BigInt, and Date validations in Zod.
Number Validations
Use z.number() to validate numbers. It allows any finite number (rejects NaN and Infinity):
import { z } from "zod";
const schema = z.number();
schema.parse(3.14); // passes
schema.parse(NaN); // fails
schema.parse(Infinity); // failsValidation Methods
import { z } from "zod";
z.number().gt(5); // greater than 5
z.number().gte(5); // greater than or equal to 5 (alias: .min(5))
z.number().lt(5); // less than 5
z.number().lte(5); // less than or equal to 5 (alias: .max(5))
z.number().positive(); // greater than 0 (alias: .gt(0))
z.number().nonnegative(); // greater than or equal to 0
z.number().negative(); // less than 0
z.number().nonpositive(); // less than or equal to 0
z.number().multipleOf(5); // divisible by 5 (alias: .step(5))NaN
To validate NaN specifically:
import { z } from "zod";
z.nan().parse(NaN); // passes
z.nan().parse("anything else"); // failsIntegers
To validate integers:
import { z } from "zod";
z.int(); // restricts to safe integer range (Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER)
z.int32(); // restricts to int32 range (-2147483648 to 2147483647)BigInts
To validate BigInt values:
import { z } from "zod";
z.bigint();BigInt Validation Methods
BigInt schemas support the same validation methods as numbers, but with BigInt values:
import { z } from "zod";
z.bigint().gt(5n); // greater than 5n
z.bigint().gte(5n); // greater than or equal to 5n (alias: .min(5n))
z.bigint().lt(5n); // less than 5n
z.bigint().lte(5n); // less than or equal to 5n (alias: .max(5n))
z.bigint().positive(); // greater than 0n (alias: .gt(0n))
z.bigint().nonnegative(); // greater than or equal to 0n
z.bigint().negative(); // less than 0n
z.bigint().nonpositive(); // less than or equal to 0n
z.bigint().multipleOf(5n); // divisible by 5n (alias: .step(5n))Dates
Use z.date() to validate Date instances:
import { z } from "zod";
z.date().safeParse(new Date()); // success: true
z.date().safeParse("2022-01-12T06:15:00.000Z"); // success: falseCustom Error Messages
import { z } from "zod";
z.date({
error: issue => issue.input === undefined ? "Required" : "Invalid date"
});Date Validations
import { z } from "zod";
z.date().min(new Date("1900-01-01"), { error: "Too old!" });
z.date().max(new Date(), { error: "Too young!" });Notes
z.number()rejectsNaNandInfinityby default -- usez.nan()if you specifically need to validateNaNz.int()restricts to the safe integer range (Number.MIN_SAFE_INTEGERtoNumber.MAX_SAFE_INTEGER)z.int32()restricts to the 32-bit signed integer rangez.date()validatesDateinstances, not date strings -- usez.iso.datetime()orz.iso.date()for string validation.gte()is aliased as.min(),.lte()is aliased as.max(),.multipleOf()is aliased as.step()
Related
- Primitives
- Strings
- Transforms and Refinements
Objects
Object schemas, property manipulation, and recursive types in Zod.
z.object()
Define an object schema. All properties are required by default:
import { z } from "zod";
const Person = z.object({
name: z.string(),
age: z.number(),
});
type Person = z.infer<typeof Person>;
// => { name: string; age: number }To make properties optional:
const Dog = z.object({
name: z.string(),
age: z.number().optional(),
});
Dog.parse({ name: "Yeller" }); // passesBy default, unrecognized keys are stripped from the parsed result:
Dog.parse({ name: "Yeller", extraKey: true });
// => { name: "Yeller" }z.strictObject()
Throws an error when unknown keys are found:
import { z } from "zod";
const StrictDog = z.strictObject({
name: z.string(),
});
StrictDog.parse({ name: "Yeller", extraKey: true });
// throws ZodErrorz.looseObject()
Allows unknown keys to pass through unchanged:
import { z } from "zod";
const LooseDog = z.looseObject({
name: z.string(),
});
LooseDog.parse({ name: "Yeller", extraKey: true });
// => { name: "Yeller", extraKey: true }.catchall()
Define a catchall schema that validates any unrecognized keys:
import { z } from "zod";
const DogWithStrings = z.object({
name: z.string(),
age: z.number().optional(),
}).catchall(z.string());
DogWithStrings.parse({ name: "Yeller", extraKey: "extraValue" }); // passes
DogWithStrings.parse({ name: "Yeller", extraKey: 42 }); // fails.shape
Access the internal schemas for each property:
Dog.shape.name; // => string schema
Dog.shape.age; // => number schema.keyof()
Create a ZodEnum schema from the keys of an object schema:
const keySchema = Dog.keyof();
// => ZodEnum<["name", "age"]>.extend()
Add additional fields to an object schema:
import { z } from "zod";
const DogWithBreed = Dog.extend({
breed: z.string(),
});This API can overwrite existing fields. Be careful with this.
Alternative: Spread Syntax
You can use spread syntax to merge object shapes. This makes the strictness level visually obvious:
import { z } from "zod";
const DogWithBreed = z.object({
...Dog.shape,
breed: z.string(),
});
// merge multiple objects
const Combined = z.object({
...Animal.shape,
...Pet.shape,
breed: z.string(),
});Spread syntax advantages: 1. Uses language-level features instead of library-specific APIs 2. Same syntax works in Zod and Zod Mini 3. More tsc-efficient (.extend() can be expensive on large schemas and gets quadratically more expensive when chained) 4. You can change the strictness level by using z.strictObject() or z.looseObject()
.safeExtend()
Works like .extend() but prevents overwriting a property with a non-assignable schema. The result's inferred type extends the original:
import { z } from "zod";
z.object({ a: z.string() }).safeExtend({ a: z.string().min(5) }); // passes
z.object({ a: z.string() }).safeExtend({ a: z.any() }); // passes
z.object({ a: z.string() }).safeExtend({ a: z.number() }); // type errorUse .safeExtend() to extend schemas that contain refinements (regular .extend() will throw on schemas with refinements):
import { z } from "zod";
const Base = z.object({
a: z.string(),
b: z.string()
}).refine(user => user.a === user.b);
// Extended inherits the refinements of Base
const Extended = Base.safeExtend({
a: z.string().min(10)
});.pick()
Pick certain keys from an object schema (inspired by TypeScript's Pick):
import { z } from "zod";
const Recipe = z.object({
title: z.string(),
description: z.string().optional(),
ingredients: z.array(z.string()),
});
const JustTheTitle = Recipe.pick({ title: true });.omit()
Omit certain keys from an object schema (inspired by TypeScript's Omit):
import { z } from "zod";
const RecipeNoDescription = Recipe.omit({ description: true });.partial()
Make some or all properties optional (inspired by TypeScript's Partial):
import { z } from "zod";
// make all fields optional
const PartialRecipe = Recipe.partial();
// { title?: string; description?: string; ingredients?: string[] }
// make specific fields optional
const RecipeOptionalIngredients = Recipe.partial({
ingredients: true,
});
// { title: string; description?: string; ingredients?: string[] }.required()
Make some or all properties required (inspired by TypeScript's Required):
import { z } from "zod";
// make all fields required
const RequiredRecipe = Recipe.required();
// { title: string; description: string; ingredients: string[] }
// make specific fields required
const RecipeRequiredDescription = Recipe.required({ description: true });
// { title: string; description: string; ingredients: string[] }Recursive Objects
Use a getter on the key to define self-referential types. JavaScript resolves the cyclical schema at runtime:
import { z } from "zod";
const Category = z.object({
name: z.string(),
get subcategories() {
return z.array(Category);
}
});
type Category = z.infer<typeof Category>;
// { name: string; subcategories: Category[] }Warning: Passing cyclical data into Zod will cause an infinite loop.
Mutually Recursive Types
import { z } from "zod";
const User = z.object({
email: z.email(),
get posts() {
return z.array(Post);
}
});
const Post = z.object({
title: z.string(),
get author() {
return User;
}
});All object APIs (.pick(), .omit(), .required(), .partial(), etc.) work with recursive types.
Resolving Circularity Errors
For complicated recursive types, TypeScript may produce circularity errors. Resolve them with a type annotation on the getter:
import { z } from "zod";
const Activity = z.object({
name: z.string(),
get subactivities(): z.ZodNullable<z.ZodArray<typeof Activity>> {
return z.nullable(z.array(Activity));
},
});Notes
z.object()strips unrecognized keys by defaultz.strictObject()throws on unrecognized keysz.looseObject()passes through unrecognized keys.extend()can overwrite existing fields -- use.safeExtend()if you want type safety.safeExtend()is required for extending schemas that have refinements- Prefer spread syntax (
...Schema.shape) over.extend()for better TypeScript performance - Recursive schemas use JavaScript getters for self-reference at runtime
Related
- Collections
- Unions and Intersections
- Enums and Literals
Primitives
Core primitive type schemas and coercion utilities in Zod.
Primitive Types
import { z } from "zod";
// primitive types
z.string();
z.number();
z.bigint();
z.boolean();
z.symbol();
z.undefined();
z.null();Each of these returns a Zod schema that validates values of the corresponding JavaScript primitive type.
Coercion
To coerce input data to the appropriate type, use z.coerce instead:
import { z } from "zod";
z.coerce.string(); // String(input)
z.coerce.number(); // Number(input)
z.coerce.boolean(); // Boolean(input)
z.coerce.bigint(); // BigInt(input)
z.coerce.date(); // new Date(input)The coerced variant attempts to convert the input value using the built-in JavaScript constructors:
const schema = z.coerce.string();
schema.parse("tuna"); // => "tuna"
schema.parse(42); // => "42"
schema.parse(true); // => "true"
schema.parse(null); // => "null"Coercion Table
| Zod API | Coercion |
|---|---|
z.coerce.string() | String(value) |
z.coerce.number() | Number(value) |
z.coerce.boolean() | Boolean(value) |
z.coerce.bigint() | BigInt(value) |
z.coerce.date() | new Date(value) |
Input Type Customization
The input type of coerced schemas is unknown by default. To specify a more specific input type, pass a generic parameter:
import { z } from "zod";
const A = z.coerce.number();
type AInput = z.input<typeof A>; // => unknown
const B = z.coerce.number<number>();
type BInput = z.input<typeof B>; // => numberBoolean Coercion Caveat
Boolean coercion with z.coerce.boolean() may not work as expected. Any truthy value is coerced to true, and any falsy value is coerced to false:
import { z } from "zod";
const schema = z.coerce.boolean(); // Boolean(input)
schema.parse("tuna"); // => true
schema.parse("true"); // => true
schema.parse("false"); // => true (non-empty string is truthy!)
schema.parse(1); // => true
schema.parse([]); // => true
schema.parse(0); // => false
schema.parse(""); // => false
schema.parse(undefined); // => false
schema.parse(null); // => falseFor total control over coercion logic, consider using z.transform() or z.pipe().
Literals
Literal schemas represent a literal type, like "hello world" or 5:
import { z } from "zod";
const tuna = z.literal("tuna");
const twelve = z.literal(12);
const twobig = z.literal(2n);
const tru = z.literal(true);To represent the JavaScript literals null and undefined:
z.null();
z.undefined();
z.void(); // equivalent to z.undefined()Multiple Literal Values
To allow multiple literal values:
import { z } from "zod";
const colors = z.literal(["red", "green", "blue"]);
colors.parse("green"); // => "green"
colors.parse("yellow"); // throws ZodError.values Property
To extract the set of allowed values from a literal schema:
colors.values; // => Set<"red" | "green" | "blue">Note: The .values property is available in Zod but not in Zod Mini.Notes
- Coercion uses built-in JavaScript constructors (
String(),Number(), etc.) -- not custom parsing logic z.coerce.boolean()uses JavaScript truthiness rules, which may be surprising (e.g."false"coerces totrue)- For more nuanced string-to-boolean coercion, use
z.stringbool()instead z.void()is functionally equivalent toz.undefined()- Literal schemas with multiple values use an array syntax:
z.literal(["a", "b", "c"])
Related
- Strings
- Numbers
- Enums and Literals
- Transforms and Refinements
API
| Name | Description | Path |
|---|---|---|
| Collections | Array, tuple, record, map, set, and file schemas in Zod. | collections.md |
| Enums and Literals | Enum schemas, boolean validation, and string-to-boolean coercion in Zod. | enums-and-literals.md |
| Numbers | Number, integer, BigInt, and Date validations in Zod. | numbers.md |
| Objects | Object schemas, property manipulation, and recursive types in Zod. | objects.md |
| Primitives | Core primitive type schemas and coercion utilities in Zod. | primitives.md |
| Special Types | Optional, nullable, nullish, unknown, never, any, promise, instanceof, JSON, function, and custom schemas in Zod. | special-types.md |
| Strings | String validation, transforms, and format schemas in Zod. | strings.md |
| Transforms and Refinements | Refinements, superRefine, codecs, pipes, transforms, defaults, catch, brand, readonly, and apply in Zod. | transforms-and-refinements.md |
| Unions and Intersections | Union, exclusive union (XOR), discriminated union, and intersection schemas in Zod. | unions-and-intersections.md |
Special Types
Optional, nullable, nullish, unknown, never, any, promise, instanceof, JSON, function, and custom schemas in Zod.
z.optional() / .optional()
Make a schema optional (allows undefined inputs):
import { z } from "zod";
z.optional(z.literal("yoda")); // or z.literal("yoda").optional()Returns a ZodOptional instance. To extract the inner schema:
const optionalYoda = z.optional(z.literal("yoda"));
optionalYoda.unwrap(); // ZodLiteral<"yoda">z.nullable() / .nullable()
Make a schema nullable (allows null inputs):
import { z } from "zod";
z.nullable(z.literal("yoda")); // or z.literal("yoda").nullable()Returns a ZodNullable instance. To extract the inner schema:
const nullableYoda = z.nullable(z.literal("yoda"));
nullableYoda.unwrap(); // ZodLiteral<"yoda">z.nullish()
Make a schema both optional and nullable:
import { z } from "zod";
const nullishYoda = z.nullish(z.literal("yoda"));
// accepts "yoda" | null | undefinedRefer to the TypeScript manual for more about the concept of nullish.
z.unknown()
Allows any value with inferred type unknown:
import { z } from "zod";
z.unknown(); // inferred type: unknownz.any()
Allows any value with inferred type any:
import { z } from "zod";
z.any(); // inferred type: anyz.never()
No value will pass validation:
import { z } from "zod";
z.never(); // inferred type: neverz.promise() (Deprecated)
Deprecated in Zod 4. If you suspect a value might be aPromise, simplyawaitit before parsing it with Zod.
import { z } from "zod";
const numberPromise = z.promise(z.number());Validation happens in two parts: 1. Zod synchronously checks that the input is a Promise (has .then and .catch methods) 2. Zod attaches an additional validation step onto the Promise via .then
numberPromise.parse("tuna");
// ZodError: Non-Promise type: string
numberPromise.parse(Promise.resolve("tuna"));
// => Promise<number>
const test = async () => {
await numberPromise.parse(Promise.resolve("tuna"));
// ZodError: Non-number type: string
await numberPromise.parse(Promise.resolve(3.14));
// => 3.14
};z.instanceof()
Check that the input is an instance of a class. Useful for validating inputs against third-party library classes:
import { z } from "zod";
class Test {
name: string;
}
const TestSchema = z.instanceof(Test);
TestSchema.parse(new Test()); // passes
TestSchema.parse("whatever"); // fails.check() with z.property()
Validate a particular property of a class instance against a Zod schema:
import { z } from "zod";
const urlSchema = z.instanceof(URL).check(
z.property("protocol", z.literal("https:" as string, "Only HTTPS allowed"))
);
urlSchema.parse(new URL("https://example.com")); // passes
urlSchema.parse(new URL("http://example.com")); // failsz.property() works with any data type, but is most useful with z.instanceof():
import { z } from "zod";
const longString = z.string().check(
z.property("length", z.number().min(10))
);
longString.parse("hello there!"); // passes
longString.parse("hello."); // failsz.json()
Validate any JSON-encodable value:
import { z } from "zod";
const jsonSchema = z.json();This is a convenience API equivalent to:
const jsonSchema = z.lazy(() => {
return z.union([
z.string(),
z.number(),
z.boolean(),
z.null(),
z.array(jsonSchema),
z.record(z.string(), jsonSchema)
]);
});z.function()
Define Zod-validated functions to separate validation from business logic:
import { z } from "zod";
const MyFunction = z.function({
input: [z.string()], // parameters (must be an array or a ZodTuple)
output: z.number() // return type
});
type MyFunction = z.infer<typeof MyFunction>;
// (input: string) => number.implement()
Accept a function and return a new function that automatically validates inputs and outputs:
import { z } from "zod";
const MyFunction = z.function({
input: [z.string()],
output: z.number()
});
const computeTrimmedLength = MyFunction.implement((input) => {
// TypeScript knows input is a string!
return input.trim().length;
});
computeTrimmedLength("sandwich"); // => 8
computeTrimmedLength(" asdf "); // => 4
computeTrimmedLength(42); // throws ZodErrorIf you only care about validating inputs, omit the output field:
const MyFunction = z.function({
input: [z.string()],
});
const fn = MyFunction.implement((input) => input.trim().length);.implementAsync()
Create an async function:
import { z } from "zod";
const MyFunction = z.function({
input: [z.string()],
output: z.number()
});
const computeTrimmedLengthAsync = MyFunction.implementAsync(
async (input) => input.trim().length
);
computeTrimmedLengthAsync("sandwich"); // => Promise<8>z.custom()
Create a schema for any TypeScript type, including types not supported by Zod out of the box:
import { z } from "zod";
const px = z.custom<`${number}px`>((val) => {
return typeof val === "string" ? /^\d+px$/.test(val) : false;
});
type px = z.infer<typeof px>; // `${number}px`
px.parse("42px"); // "42px"
px.parse("42vw"); // throws ZodErrorIf you do not provide a validation function, Zod will allow any value (dangerous!):
z.custom<{ arg: string }>(); // performs no validationCustom error messages:
z.custom<string>((val) => typeof val === "string", "custom error message");Notes
.optional()wraps withZodOptional,.nullable()wraps withZodNullable-- both support.unwrap()to get the inner schemaz.promise()is deprecated in Zod 4 --awaitvalues before parsing insteadz.instanceof()uses JavaScript'sinstanceofoperator under the hoodz.property()is primarily intended for use withz.instanceof()but works on any data typez.json()is a recursive union schema covering all JSON-encodable typesz.function()validates both inputs and outputs; omitoutputto only validate inputsz.custom()without a validation function is dangerous -- it performs no runtime validation
Related
- Primitives
- Transforms and Refinements
- Unions and Intersections
Strings
String validation, transforms, and format schemas in Zod.
String Validations
import { z } from "zod";
z.string().max(5);
z.string().min(5);
z.string().length(5);
z.string().regex(/^[a-z]+$/);
z.string().startsWith("aaa");
z.string().endsWith("zzz");
z.string().includes("---");
z.string().uppercase();
z.string().lowercase();All validation APIs support the error parameter for customizing the error message:
z.string().startsWith("fourscore", { error: "Nice try, buddy" });String Transforms
import { z } from "zod";
z.string().trim(); // trim whitespace
z.string().toLowerCase(); // convert to lowercase
z.string().toUpperCase(); // convert to uppercase
z.string().normalize(); // normalize unicode charactersString Formats
Zod provides built-in string format validators as top-level functions:
import { z } from "zod";
z.email();
z.uuid();
z.url();
z.httpUrl(); // http or https URLs only
z.hostname();
z.e164(); // E.164 phone number format
z.emoji(); // validates a single emoji character
z.base64();
z.base64url();
z.hex();
z.jwt();
z.nanoid();
z.cuid();
z.cuid2();
z.ulid();
z.ipv4();
z.ipv6();
z.mac();
z.cidrv4(); // ipv4 CIDR block
z.cidrv6(); // ipv6 CIDR block
z.hash("sha256"); // or "sha1", "sha384", "sha512", "md5"
z.iso.date();
z.iso.time();
z.iso.datetime();
z.iso.duration();Emails
import { z } from "zod";
z.email();By default, Zod uses a strict email regex designed to validate common email addresses. To customize validation:
z.email({ pattern: /your regex here/ });Zod exports several useful email regexes:
// Zod's default email regex
z.email();
z.email({ pattern: z.regexes.email }); // equivalent
// browser input[type=email] regex
z.email({ pattern: z.regexes.html5Email });
// RFC 5322 regex (emailregex.com)
z.email({ pattern: z.regexes.rfc5322Email });
// loose regex that allows Unicode (good for intl emails)
z.email({ pattern: z.regexes.unicodeEmail });UUIDs
import { z } from "zod";
z.uuid();
// specify a particular UUID version ("v1" through "v8")
z.uuid({ version: "v4" });
// convenience aliases
z.uuidv4();
z.uuidv6();
z.uuidv7();GUIDs
The RFC 9562/4122 UUID spec requires the first two bits of byte 8 to be 10. To validate any UUID-like identifier without this constraint:
z.guid();URLs
import { z } from "zod";
const schema = z.url();
schema.parse("https://example.com"); // passes
schema.parse("http://localhost"); // passes
schema.parse("mailto:noreply@zod.dev"); // passesInternally this uses the new URL() constructor. To validate the hostname or protocol against a regex:
// validate hostname
const schema = z.url({ hostname: /^example\.com$/ });
// validate protocol
const schema = z.url({ protocol: /^https$/ });For validating web URLs specifically:
const httpUrl = z.url({
protocol: /^https?$/,
hostname: z.regexes.domain
});To normalize URLs with the normalize flag:
const schema = z.url({ normalize: true });
// "HTTP://ExAmPle.com:80/./a/../b?X=1#f oo" => "http://example.com/b?X=1#f%20oo"ISO Datetimes
Enforces ISO 8601. By default, no timezone offsets are allowed:
import { z } from "zod";
const datetime = z.iso.datetime();
datetime.parse("2020-01-01T06:15:00Z"); // passes
datetime.parse("2020-01-01T06:15:00.123Z"); // passes
datetime.parse("2020-01-01T06:15:00.123456Z"); // passes (arbitrary precision)
datetime.parse("2020-01-01T06:15:00+02:00"); // fails (offsets not allowed)
datetime.parse("2020-01-01T06:15:00"); // fails (local not allowed)Options:
// allow timezone offsets
z.iso.datetime({ offset: true });
// allow unqualified (timezone-less) datetimes
z.iso.datetime({ local: true });
// constrain time precision
z.iso.datetime({ precision: -1 }); // minute precision (no seconds)
z.iso.datetime({ precision: 0 }); // second precision only
z.iso.datetime({ precision: 3 }); // millisecond precision onlyISO Dates
Validates strings in the format YYYY-MM-DD:
import { z } from "zod";
const date = z.iso.date();
date.parse("2020-01-01"); // passes
date.parse("2020-1-1"); // fails
date.parse("2020-01-32"); // failsISO Times
Validates strings in the format HH:MM[:SS[.s+]]. Seconds are optional by default:
import { z } from "zod";
const time = z.iso.time();
time.parse("03:15"); // passes
time.parse("03:15:00"); // passes
time.parse("03:15:00.9999999"); // passes (arbitrary precision)
time.parse("03:15:00Z"); // fails (no Z allowed)
time.parse("03:15:00+02:00"); // fails (no offsets allowed)Precision options:
z.iso.time({ precision: -1 }); // HH:MM (minute precision)
z.iso.time({ precision: 0 }); // HH:MM:SS (second precision)
z.iso.time({ precision: 3 }); // HH:MM:SS.sss (millisecond precision)Phone Numbers (E.164)
import { z } from "zod";
const phone = z.e164();
phone.parse("+15555555555"); // passes
phone.parse("555-555-5555"); // failsThe schema validates strings with a leading +, a non-zero country code, and 7 to 15 digits total.
Hostnames
import { z } from "zod";
z.hostname();IP Addresses
import { z } from "zod";
const ipv4 = z.ipv4();
ipv4.parse("192.168.0.0"); // passes
const ipv6 = z.ipv6();
ipv6.parse("2001:db8:85a3::8a2e:370:7334"); // passesIP Blocks (CIDR)
import { z } from "zod";
const cidrv4 = z.cidrv4();
cidrv4.parse("192.168.0.0/24"); // passes
const cidrv6 = z.cidrv6();
cidrv6.parse("2001:db8::/32"); // passesMAC Addresses
Validate standard 48-bit MAC addresses (IEEE 802):
import { z } from "zod";
const mac = z.mac();
mac.parse("00:1A:2B:3C:4D:5E"); // passes
mac.parse("00-1a-2b-3c-4d-5e"); // fails (colon-delimited by default)
mac.parse("00:1A:2b:3C:4d:5E"); // fails (no mixed case)
// custom delimiter
const dashMac = z.mac({ delimiter: "-" });
dashMac.parse("00-1A-2B-3C-4D-5E"); // passesJWTs
import { z } from "zod";
z.jwt();
z.jwt({ alg: "HS256" });Hashes
import { z } from "zod";
z.hash("md5");
z.hash("sha1");
z.hash("sha256");
z.hash("sha384");
z.hash("sha512");
// specify encoding (default is "hex")
z.hash("sha256", { enc: "hex" }); // hexadecimal (default)
z.hash("sha256", { enc: "base64" }); // base64 encoding
z.hash("sha256", { enc: "base64url" }); // base64url encoding (no padding)Custom String Formats
Define your own string formats using z.stringFormat():
import { z } from "zod";
const coolId = z.stringFormat("cool-id", (val) => {
return val.length === 100 && val.startsWith("cool-");
});
// a regex is also accepted
z.stringFormat("cool-id", /^cool-[a-z0-9]{95}$/);Custom formats produce "invalid_format" issues, which are more descriptive than the "custom" errors produced by refinements.
Template Literals
Define template literal schemas (introduced in Zod 4):
import { z } from "zod";
const schema = z.templateLiteral(["hello, ", z.string(), "!"]);
// `hello, ${string}!`
z.templateLiteral(["hi there"]);
// `hi there`
z.templateLiteral(["email: ", z.string()]);
// `email: ${string}`
z.templateLiteral(["high", z.literal(5)]);
// `high5`
z.templateLiteral([z.nullable(z.literal("grassy"))]);
// `grassy` | `null`
z.templateLiteral([z.number(), z.enum(["px", "em", "rem"])]);
// `${number}px` | `${number}em` | `${number}rem`Any schema with an inferred type assignable to string | number | bigint | boolean | null | undefined can be passed as an element.
Notes
- All string validations support a custom
errorparameter for error messages - String format validators (e.g.
z.email()) are top-level functions, not methods onz.string() z.iso.datetime()uses regex-based validation -- not as strict as a full date/time libraryz.url()internally uses thenew URL()constructor; behavior may differ across runtimesz.mac()is colon-delimited and case-sensitive by defaultz.hash()expects hexadecimal encoding by defaultz.e164()validates E.164 phone numbers (leading+, non-zero country code, 7-15 digits total)- Template literals are new in Zod 4
Related
- Primitives
- Numbers
- Transforms and Refinements
Transforms and Refinements
Refinements, superRefine, codecs, pipes, transforms, defaults, catch, brand, readonly, and apply in Zod.
.refine()
Perform custom validation that Zod does not provide a native API for:
import { z } from "zod";
const myString = z.string().refine((val) => val.length <= 255);Refinement functions should never throw. Return a falsy value to signal failure. Thrown errors are not caught by Zod.
error Parameter
Customize the error message:
import { z } from "zod";
const myString = z.string().refine((val) => val.length > 8, {
error: "Too short!"
});abort Parameter
By default, validation issues from checks are continuable -- Zod executes all checks even if one fails. Use abort to stop on failure:
import { z } from "zod";
const myString = z.string()
.refine((val) => val.length > 8, { error: "Too short!", abort: true })
.refine((val) => val === val.toLowerCase(), { error: "Must be lowercase", abort: true });
const result = myString.safeParse("OH NO");
result.error?.issues;
// => [{ "code": "custom", "message": "Too short!" }]
// Second refinement did NOT run because first was abortedWithout abort, both errors would be reported:
const myString = z.string()
.refine((val) => val.length > 8, { error: "Too short!" })
.refine((val) => val === val.toLowerCase(), { error: "Must be lowercase" });
const result = myString.safeParse("OH NO");
result.error?.issues;
// => [
// { "code": "custom", "message": "Too short!" },
// { "code": "custom", "message": "Must be lowercase" }
// ]path Parameter
Customize the error path (useful with object schemas):
import { z } from "zod";
const passwordForm = z
.object({
password: z.string(),
confirm: z.string(),
})
.refine((data) => data.password === data.confirm, {
message: "Passwords don't match",
path: ["confirm"],
});
const result = passwordForm.safeParse({ password: "asdf", confirm: "qwer" });
result.error.issues;
// [{ "code": "custom", "path": ["confirm"], "message": "Passwords don't match" }]Async Refinements
Pass an async function for asynchronous validation:
import { z } from "zod";
const userId = z.string().refine(async (id) => {
// verify that ID exists in database
return true;
});
// Must use parseAsync for async refinements
const result = await userId.parseAsync("abc123");when Parameter
Control when a refinement runs. By default, refinements do not run if any non-continuable issues have been encountered:
import { z } from "zod";
const schema = z
.object({
password: z.string().min(8),
confirmPassword: z.string(),
anotherField: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
// run if password & confirmPassword are valid
when(payload) {
return schema
.pick({ password: true, confirmPassword: true })
.safeParse(payload.value).success;
},
});.superRefine() (Deprecated)
Deprecated in favor of.check(). Existing code using.superRefine()continues to work, but new code should use.check()instead.
Generate multiple issues using any of Zod's internal issue types:
import { z } from "zod";
const UniqueStringArray = z.array(z.string()).superRefine((val, ctx) => {
if (val.length > 3) {
ctx.addIssue({
code: "too_big",
maximum: 3,
origin: "array",
inclusive: true,
message: "Too many items",
input: val,
});
}
if (val.length !== new Set(val).size) {
ctx.addIssue({
code: "custom",
message: "No duplicates allowed.",
input: val,
});
}
});.check()
A low-level API that provides full control over generated issue objects. More verbose but can be faster in performance-sensitive code:
import { z } from "zod";
const UniqueStringArray = z.array(z.string()).check((ctx) => {
if (ctx.value.length > 3) {
ctx.issues.push({
code: "too_big",
maximum: 3,
origin: "array",
inclusive: true,
message: "Too many items",
input: ctx.value,
});
}
if (ctx.value.length !== new Set(ctx.value).size) {
ctx.issues.push({
code: "custom",
message: "No duplicates allowed.",
input: ctx.value,
continue: true, // make this issue continuable (default: false)
});
}
});z.codec()
Codecs implement bidirectional transformations between two schemas (introduced in Zod 4.1):
import { z } from "zod";
const stringToDate = z.codec(
z.iso.datetime(), // input schema: ISO date string
z.date(), // output schema: Date object
{
decode: (isoString) => new Date(isoString), // ISO string -> Date
encode: (date) => date.toISOString(), // Date -> ISO string
}
);decode() (Forward Transform)
A regular .parse() calls the codec's decode function:
stringToDate.parse("2024-01-15T10:30:00.000Z"); // => Datez.decode()
Strongly-typed input alternative to .parse():
import { z } from "zod";
z.decode(stringToDate, "2024-01-15T10:30:00.000Z"); // => Datez.encode() (Reverse Transform)
Perform the reverse transformation:
import { z } from "zod";
z.encode(stringToDate, new Date("2024-01-15")); // => "2024-01-15T00:00:00.000Z".pipe()
Chain schemas together into pipes. Primarily useful with transforms:
import { z } from "zod";
const stringToLength = z.string().pipe(z.transform(val => val.length));
stringToLength.parse("hello"); // => 5z.transform() / .transform()
Transforms perform unidirectional transformation on data. They accept anything and transform it:
import { z } from "zod";
const castToString = z.transform((val) => String(val));
castToString.parse("asdf"); // => "asdf"
castToString.parse(123); // => "123"
castToString.parse(true); // => "true"Transform functions should never throw. Thrown errors are not caught by Zod.
Validation Inside Transforms
Report validation issues by pushing onto ctx.issues:
import { z } from "zod";
const coercedInt = z.transform((val, ctx) => {
try {
return Number.parseInt(String(val));
} catch (e) {
ctx.issues.push({
code: "custom",
message: "Not a number",
input: val,
});
return z.NEVER; // exit without impacting inferred return type
}
});.transform() Convenience Method
Piping a schema into a transform is common, so Zod provides a shortcut:
import { z } from "zod";
const stringToLength = z.string().transform(val => val.length);Async Transforms
import { z } from "zod";
const idToUser = z
.string()
.transform(async (id) => {
return db.getUserById(id);
});
const user = await idToUser.parseAsync("abc123");If you use async transforms, you must use.parseAsyncor.safeParseAsyncwhen parsing.
z.preprocess()
Pipe a transform into another schema. Convenience for pre-processing input before validation:
import { z } from "zod";
const coercedInt = z.preprocess((val) => {
if (typeof val === "string") {
return Number.parseInt(val);
}
return val;
}, z.int());.default()
Set a default value for undefined inputs. The default value is eagerly returned (short-circuits parsing):
import { z } from "zod";
const defaultTuna = z.string().default("tuna");
defaultTuna.parse(undefined); // => "tuna"Pass a function for dynamic defaults:
import { z } from "zod";
const randomDefault = z.number().default(Math.random);
randomDefault.parse(undefined); // => 0.4413456736055323
randomDefault.parse(undefined); // => 0.1871840107401901.prefault()
Set a pre-parse default. Unlike .default(), the prefault value is parsed (not short-circuited). The prefault must be assignable to the input type:
import { z } from "zod";
const schema = z.string().transform(val => val.length).prefault("tuna");
schema.parse(undefined); // => 4 (parses "tuna", then transforms to length)Comparison with .default():
import { z } from "zod";
const a = z.string().trim().toUpperCase().prefault(" tuna ");
a.parse(undefined); // => "TUNA" (prefault is parsed through the chain)
const b = z.string().trim().toUpperCase().default(" tuna ");
b.parse(undefined); // => " tuna " (default short-circuits, no trim/uppercase).catch()
Define a fallback value returned on validation error:
import { z } from "zod";
const numberWithCatch = z.number().catch(42);
numberWithCatch.parse(5); // => 5
numberWithCatch.parse("tuna"); // => 42Pass a function for dynamic catch values:
import { z } from "zod";
const numberWithRandomCatch = z.number().catch((ctx) => {
ctx.error; // the caught ZodError
return Math.random();
});
numberWithRandomCatch.parse("sup"); // => 0.4413456736055323.brand<>()
Simulate nominal typing with branded types:
import { z } from "zod";
const Cat = z.object({ name: z.string() }).brand<"Cat">();
const Dog = z.object({ name: z.string() }).brand<"Dog">();
type Cat = z.infer<typeof Cat>; // { name: string } & z.$brand<"Cat">
type Dog = z.infer<typeof Dog>; // { name: string } & z.$brand<"Dog">
const pluto = Dog.parse({ name: "pluto" });
const simba: Cat = pluto; // type error -- not assignableBy default, only the output type is branded. Customize with a second generic (requires Zod 4.2+):
import { z } from "zod";
z.string().brand<"Cat", "out">(); // output is branded (default)
z.string().brand<"Cat", "in">(); // input is branded
z.string().brand<"Cat", "inout">(); // both are brandedNote: Branded types are a static-only construct. They do not affect the runtime result of .parse()..readonly()
Mark a schema as readonly. The parsed result is frozen with Object.freeze():
import { z } from "zod";
const ReadonlyUser = z.object({ name: z.string() }).readonly();
type ReadonlyUser = z.infer<typeof ReadonlyUser>;
// Readonly<{ name: string }>
const result = ReadonlyUser.parse({ name: "fido" });
result.name = "simba"; // throws TypeErrorWorks with objects, arrays, tuples, Set, and Map:
import { z } from "zod";
z.object({ name: z.string() }).readonly(); // { readonly name: string }
z.array(z.string()).readonly(); // readonly string[]
z.tuple([z.string(), z.number()]).readonly(); // readonly [string, number]
z.map(z.string(), z.date()).readonly(); // ReadonlyMap<string, Date>
z.set(z.string()).readonly(); // ReadonlySet<string>.apply()
Incorporate external functions into Zod's method chain:
import { z } from "zod";
function setCommonNumberChecks<T extends z.ZodNumber>(schema: T) {
return schema
.min(0)
.max(100);
}
const schema = z.number()
.apply(setCommonNumberChecks)
.nullable();
schema.parse(0); // => 0
schema.parse(-1); // throws ZodError
schema.parse(101); // throws ZodError
schema.parse(null); // => nullNotes
.refine()generates a single issue with a"custom"error code.superRefine()is deprecated -- use.check()instead for new code.check()is a lower-level, more verbose API -- it can be faster but is less ergonomic- Refinement functions must never throw -- return falsy to signal failure
- Async refinements and transforms require
.parseAsync()/.safeParseAsync() z.codec()(Zod 4.1+) supports bidirectional transforms withz.decode()andz.encode().default()short-circuits parsing;.prefault()does not.catch()returns the fallback value on any validation error.brand<>()is static-only -- it does not affect runtime behavior.readonly()callsObject.freeze()on the parsed result
Related
- Primitives
- Special Types
- Objects
- Collections
Unions and Intersections
Union, exclusive union (XOR), discriminated union, and intersection schemas in Zod.
z.union()
Union types (A | B) represent a logical "OR". Zod checks the input against each option in order and returns the first value that validates:
import { z } from "zod";
const stringOrNumber = z.union([z.string(), z.number()]);
// string | number
stringOrNumber.parse("foo"); // passes
stringOrNumber.parse(14); // passes.options
Extract the internal option schemas:
stringOrNumber.options; // [ZodString, ZodNumber]z.xor() (Exclusive Unions)
An exclusive union (XOR) is a union where exactly one option must match. Fails if zero options match OR if multiple options match:
import { z } from "zod";
const schema = z.xor([z.string(), z.number()]);
schema.parse("hello"); // passes
schema.parse(42); // passes
schema.parse(true); // fails (zero matches)Useful for ensuring mutual exclusivity between options:
import { z } from "zod";
const payment = z.xor([
z.object({ type: z.literal("card"), cardNumber: z.string() }),
z.object({ type: z.literal("bank"), accountNumber: z.string() }),
]);
payment.parse({ type: "card", cardNumber: "1234" }); // passesIf the input could match multiple options, z.xor() will fail:
const overlapping = z.xor([z.string(), z.any()]);
overlapping.parse("hello"); // fails (matches both string and any)z.discriminatedUnion()
A discriminated union uses a shared "discriminator" key to efficiently parse the correct option. More efficient than z.union() for large unions:
import { z } from "zod";
const MyResult = z.discriminatedUnion("status", [
z.object({ status: z.literal("success"), data: z.string() }),
z.object({ status: z.literal("failed"), error: z.string() }),
]);Each option should be an object schema whose discriminator property corresponds to some literal value, typically z.literal(), z.enum(), z.null(), or z.undefined().
Type Narrowing
TypeScript can narrow the type based on the discriminator:
type MyResult =
| { status: "success"; data: string }
| { status: "failed"; error: string };
function handleResult(result: MyResult) {
if (result.status === "success") {
result.data; // string
} else {
result.error; // string
}
}Nesting Discriminated Unions
Discriminated unions can be nested. Zod determines the optimal parsing strategy to leverage discriminators at each level:
import { z } from "zod";
const BaseError = { status: z.literal("failed"), message: z.string() };
const MyErrors = z.discriminatedUnion("code", [
z.object({ ...BaseError, code: z.literal(400) }),
z.object({ ...BaseError, code: z.literal(401) }),
z.object({ ...BaseError, code: z.literal(500) }),
]);
const MyResult = z.discriminatedUnion("status", [
z.object({ status: z.literal("success"), data: z.string() }),
MyErrors
]);z.intersection()
Intersection types (A & B) represent a logical "AND":
import { z } from "zod";
const a = z.union([z.number(), z.string()]);
const b = z.union([z.number(), z.boolean()]);
const c = z.intersection(a, b);
type c = z.infer<typeof c>; // => numberUseful for intersecting two object types:
import { z } from "zod";
const Person = z.object({ name: z.string() });
const Employee = z.object({ role: z.string() });
const EmployedPerson = z.intersection(Person, Employee);
type EmployedPerson = z.infer<typeof EmployedPerson>;
// Person & EmployeeWarning: When merging object schemas, preferA.extend(B.shape)over intersections. Using.extend()gives you a new object schema with methods like.pick()and.omit(), whereasz.intersection()returns aZodIntersectioninstance which lacks those methods.
Notes
z.union()checks options in order and returns the first match -- order can matter for performancez.xor()fails when zero or multiple options match, making it stricter thanz.union()z.discriminatedUnion()is more efficient thanz.union()for large unions of objects that share a common discriminator keyz.intersection()returns aZodIntersectionwhich lacks object methods like.pick(),.omit(), etc.- Prefer
.extend()or spread syntax for merging objects instead ofz.intersection() - Discriminated unions can be nested for advanced use cases
Related
- Objects
- Enums and Literals
- Collections
Ecosystem
Overview of the Zod ecosystem including integrations, tools, and community resources.
Note -- The Ecosystem section was wiped clean with the release of Zod 4. Libraries listed here have been updated to work with Zod 4. For libraries that work with Zod 3, refer to v3.zod.dev.
Resources
- Total TypeScript Zod Tutorial by @mattpocockuk
- Fixing TypeScript's Blindspot: Runtime Typechecking by @jherr
- Validate Environment Variables With Zod by @catalinmpit
API Libraries
| Name | Description |
|---|---|
| `tRPC` | Build end-to-end typesafe APIs without GraphQL. |
| `upfetch` | Advanced fetch client builder. |
| `nestjs-zod` | Integrate nestjs and zod. Create nestjs DTOs using zod, serialize with zod, and generate OpenAPI documentation from zod schemas. |
| `Express Zod API` | Build Express-based API with I/O validation and middlewares, OpenAPI docs and type-safe client. |
| `Zod Sockets` | Socket.IO solution with I/O validation, an AsyncAPI generator, and a type-safe events map. |
| `GQLoom` | Weave GraphQL schema and resolvers using Zod. |
| `Zod JSON-RPC` | Type-safe JSON-RPC 2.0 client/server library using Zod. |
| `oRPC` | Typesafe APIs Made Simple. |
Form Integrations
| Name | Description |
|---|---|
| `Superforms` | Making SvelteKit forms a pleasure to use! |
| `conform` | A type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix and Next.js. |
| `zod-validation-error` | Generate user-friendly error messages from ZodError instances. |
| `regle` | Headless form validation library for Vue.js. |
| `svelte-jsonschema-form` | Svelte 5 library for creating forms based on JSON schema. |
| `frrm` | Tiny 0.5kb Zod-based, HTML form abstraction that goes brr. |
| `react-f3` | Components, hooks & utilities for creating and managing delightfully simple form experiences in React. |
Zod to X
| Name | Description |
|---|---|
| `prisma-zod-generator` | Generate Zod schemas from Prisma schema with full ZodObject method support. |
| `zod-openapi` | Use Zod Schemas to create OpenAPI v3.x documentation. |
| `convex-helpers` | Use Zod to validate arguments and return values of Convex functions, and to create Convex database schemas. |
| `@traversable/zod` | Build your own "Zod to x" library, or pick one of 25+ off-the-shelf transformers. |
| `zod2md` | Generate Markdown docs from Zod schemas. |
| `fastify-zod-openapi` | Fastify type provider, validation, serialization and @fastify/swagger support for Zod schemas. |
| `zod-to-mongo-schema` | Convert Zod schemas to MongoDB-compatible JSON Schemas effortlessly. |
X to Zod
| Name | Description |
|---|---|
| `orval` | Generate Zod schemas from OpenAPI schemas. |
| `Hey API` | The OpenAPI to TypeScript codegen. Generate clients, SDKs, validators, and more. |
| `kubb` | The ultimate toolkit for working with APIs. |
| `Prisma Zod Generator` | Generates Zod schemas with input/result/pure variants, minimal/full/custom, selective emit/filtering, single/multi-file output, @zod rules, relation depth guards. |
| `convex-helpers` | Generate Zod schemas from Convex validators. |
| `DRZL` | Drizzle ORM toolkit that can generate Zod validators from schema(s), plus typed services and strongly typed routers (oRPC/tRPC/etc). |
| `valype` | Typescript's type definition to runtime validator (including zod). |
| `Hono Takibi` | Hono Takibi is a code generator from OpenAPI to @hono/zod-openapi. |
Mocking Libraries
| Name | Description |
|---|---|
| `@traversable/zod-test` | Random zod schema generator built for fuzz testing; includes generators for both valid and invalid data. |
| `zod-schema-faker` | Generate mock data from zod schemas. Powered by @faker-js/faker and randexp.js. |
| `zocker` | Generates valid, semantically meaningful data for your Zod schemas. |
Powered by Zod
| Name | Description |
|---|---|
| `Composable Functions` | Types and functions to make composition easy and safe. |
| `zod-config` | Load configurations across multiple sources with flexible adapters, ensuring type safety with Zod. |
| `zod-xlsx` | A xlsx based resource validator using Zod schemas for data imports and more. |
| `Fn Sphere` | A Zod-first toolkit for building powerful, type-safe filter experiences across web apps. |
| `zodgres` | Postgres.js + Zod: Database collections with static type inference and automatic migrations. |
| `bupkis` | Uncommonly extensible assertions for the beautiful people. |
Zod Utilities
| Name | Description |
|---|---|
| `zod-playground` | Interactive playground for testing and exploring Zod and Zod mini schemas in real-time. |
| `zod-ir` | Comprehensive validation for Iranian data structures (National Code, Bank Cards, Sheba, Crypto, etc) with smart metadata extraction (Bank Names, Logos). Zero dependencies. |
| `eslint-plugin-zod-x` | ESLint plugin that adds custom linting rules to enforce best practices when using Zod. |
| `eslint-plugin-import-zod` | ESLint plugin to enforce namespace imports for Zod. |
| `Zod Compare` | A utility library for recursively comparing Zod schemas. |
| `babel-plugin-zod-hoist` | Babel plugin that optimizes Zod performance by hoisting schema definitions to the top of the file, avoiding repeated initialization overhead. |
Related
- For Library Authors
For Library Authors
Guidelines and best practices for library authors integrating with Zod.
Update -- July 10th, 2025: Zod4.0.0has been released onnpm. This completes the incremental rollout process. To add support, bump your peer dependency to includezod@^4.0.0. If you'd already implemented Zod 4 support using the"zod/v4/core"subpath, no other code changes should be necessary. This should not require a major version bump in your library.
Do I Need to Depend on Zod?
First, make sure you actually need to depend on Zod.
If you're building a library that accepts user-defined schemas to perform black-box validation, you may not need to integrate with Zod specifically. Instead look into Standard Schema. It's a shared interface implemented by most popular validation libraries in the TypeScript ecosystem (see the full list), including Zod.
The Standard Schema spec works great if you accept user-defined schemas and treat them like "black box" validators. Given any compliant library, you can extract inferred input/output types, validate inputs, and get back a standardized error.
If you need Zod-specific functionality, read on.
How to Configure Peer Dependencies
Any library built on top of Zod should include "zod" in "peerDependencies". This lets your users "bring their own Zod".
// package.json
{
// ...
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0" // the "zod/v4" subpath was added in 3.25.0
}
}During development, you need to meet your own peer dependency requirement. Add "zod" to your "devDependencies" as well:
// package.json
{
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"devDependencies": {
// generally, you should develop against the latest version of Zod
"zod": "^3.25.0 || ^4.0.0"
}
}How to Support Zod 4
To support Zod 4, update the minimum version for your "zod" peer dependency to ^3.25.0 || ^4.0.0.
// package.json
{
// ...
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
}Starting with v3.25.0, the Zod 4 core package is available at the "zod/v4/core" subpath. Read the Versioning in Zod 4 writeup for full context on this versioning approach.
import * as z4 from "zod/v4/core";Approved Subpaths
Import from these subpaths only. Think of them like "permalinks" to their respective Zod versions. These will remain available forever.
"zod/v3"for Zod 3"zod/v4/core"for the Zod 4 Core package
Subpaths to Avoid
You generally should not be importing from any other paths. The Zod Core library is a shared library that undergirds both Zod 4 Classic and Zod 4 Mini. It's generally a bad idea to implement any functionality that is specific to one or the other. Do not import from these subpaths:
"zod"-- In 3.x releases, this exports Zod 3. In 4.x releases, this will export Zod 4. Use the permalinks instead."zod/v4"and"zod/v4/mini"-- These subpaths are the homes of Zod 4 Classic and Mini, respectively. If you want your library to work with both Zod and Zod Mini, you should build against the base classes defined in"zod/v4/core". If you reference classes from the"zod/v4"module, your library will not work with Zod Mini, and vice versa. Use"zod/v4/core"instead, which exports the$-prefixed subclasses that are extended by Zod Classic and Zod Mini. The internals of the classic and mini subclasses are identical; they only differ in which helper methods they implement.
Do I Need to Publish a New Major Version?
No, you should not need to publish a new major version of your library to support Zod 4 (unless you are dropping support for Zod 3, which is not recommended).
You will need to bump your peer dependency to ^3.25.0, thus your users will need to npm upgrade zod. But there were no breaking changes made to Zod 3 between zod@3.24 and zod@3.25; in fact, there were no code changes whatsoever. As no code changes will be required on the part of your users, this does not constitute a breaking change. Publishing a new major version is not recommended.
How to Support Zod 3 and Zod 4 Simultaneously
Starting in v3.25.0, the package contains copies of both Zod 3 and Zod 4 at their respective subpaths. This makes it easy to support both versions simultaneously.
import * as z3 from "zod/v3";
import * as z4 from "zod/v4/core";
type Schema = z3.ZodTypeAny | z4.$ZodType;
function acceptUserSchema(schema: z3.ZodTypeAny | z4.$ZodType) {
// ...
}Differentiating at Runtime
To differentiate between Zod 3 and Zod 4 schemas at runtime, check for the "_zod" property. This property is only defined on Zod 4 schemas.
import type * as z3 from "zod/v3";
import type * as z4 from "zod/v4/core";
declare const schema: z3.ZodTypeAny | z4.$ZodType;
if ("_zod" in schema) {
schema._zod.def; // Zod 4 schema
} else {
schema._def; // Zod 3 schema
}How to Support Zod and Zod Mini Simultaneously
Your library code should only import from "zod/v4/core". This sub-package defines the interfaces, classes, and utilities that are shared between Zod and Zod Mini.
// library code
import * as z4 from "zod/v4/core";
export function acceptObjectSchema<T extends z4.$ZodObject>(schema: T) {
// parse data
z4.parse(schema, { /* somedata */ });
// inspect internals
schema._zod.def.shape;
}By building against the shared base interfaces, you can reliably support both sub-packages simultaneously. The function above can accept both Zod and Zod Mini schemas:
// user code
import { acceptObjectSchema } from "your-library";
// Zod 4
import * as z from "zod";
acceptObjectSchema(z.object({ name: z.string() }));
// Zod 4 Mini
import * as zm from "zod/mini";
acceptObjectSchema(zm.object({ name: zm.string() }));Refer to the Zod Core page for more information on the contents of the core sub-library.
How to Accept User-Defined Schemas
Accepting user-defined schemas is a fundamental operation for any library built on Zod.
Avoid Parameterized $ZodType
When starting out, it may be tempting to write a function that accepts a Zod schema like this:
import * as z4 from "zod/v4/core";
function inferSchema<T>(schema: z4.$ZodType<T>) {
return schema;
}This approach is incorrect, and limits TypeScript's ability to properly infer the argument. No matter what you pass in, the type of schema will be an instance of $ZodType:
inferSchema(z.string());
// => $ZodType<string>This loses type information, namely which subclass the input actually is (in this case, ZodString). That means you cannot call any string-specific methods like .min() on the result of inferSchema.
Use extends Constraints Instead
Your generic parameter should extend the core Zod schema interface:
function inferSchema<T extends z4.$ZodType>(schema: T) {
return schema;
}
inferSchema(z.string());
// => ZodStringConstraining to a Specific Subclass
To constrain the input schema to a specific subclass:
import * as z4 from "zod/v4/core";
// only accepts object schemas
function inferSchema<T extends z4.$ZodObject>(schema: T) {
return schema;
}Constraining the Output Type
To constrain the inferred output type of the input schema:
import * as z4 from "zod/v4/core";
// only accepts string schemas
function inferSchema<T extends z4.$ZodType<string>>(schema: T) {
return schema;
}
inferSchema(z.string()); // ok
inferSchema(z.number());
// Error: The types of '_zod.output' are incompatible between these types.
// Type 'number' is not assignable to type 'string'Top-Level Parsing Functions
To parse data with the schema, use the top-level z4.parse / z4.safeParse / z4.parseAsync / z4.safeParseAsync functions. The z4.$ZodType subclass has no methods on it. The usual parsing methods are implemented by Zod and Zod Mini, but are not available in Zod Core.
function parseData<T extends z4.$ZodType>(data: unknown, schema: T): z4.output<T> {
return z4.parse(schema, data);
}
parseData("sup", z.string());
// => stringRelated
- Ecosystem
Ecosystem
| Name | Description | Path |
|---|---|---|
| Ecosystem | Overview of the Zod ecosystem including integrations, tools, and community resources. | ecosystem.md |
| For Library Authors | Guidelines and best practices for library authors integrating with Zod. | library-authors.md |
Error Customization
Customize validation error messages at the schema level, per-parse level, or globally, with full internationalization support.
ZodError and Issues
Validation errors are instances of z.core.$ZodError (the ZodError class in the zod package is a subclass with additional convenience methods). Each error contains an .issues array where every issue has a human-readable message plus structured metadata.
import * as z from "zod";
const result = z.string().safeParse(12);
// { success: false, error: ZodError }
result.error.issues;
// [
// {
// expected: 'string',
// code: 'invalid_type',
// path: [],
// message: 'Invalid input: expected string, received number'
// }
// ]The error Parameter
Virtually every Zod API accepts an optional error message as a string.
z.string("Not a string!");The custom error shows up as the message property of any validation issues originating from that schema.
z.string("Not a string!").parse(12);
// throws ZodError {
// issues: [
// {
// expected: 'string',
// code: 'invalid_type',
// path: [],
// message: 'Not a string!' // custom error message
// }
// ]
// }All z functions and schema methods accept custom errors:
z.string("Bad!");
z.string().min(5, "Too short!");
z.uuid("Bad UUID!");
z.iso.date("Bad date!");
z.array(z.string(), "Not an array!");
z.array(z.string()).min(5, "Too few items!");
z.set(z.string(), "Bad set!");You can also pass a params object with an error property:
z.string({ error: "Bad!" });
z.string().min(5, { error: "Too short!" });
z.uuid({ error: "Bad UUID!" });
z.iso.date({ error: "Bad date!" });
z.array(z.string(), { error: "Bad array!" });
z.array(z.string()).min(5, { error: "Too few items!" });
z.set(z.string(), { error: "Bad set!" });Error Map Functions
The error param optionally accepts a function (known as an error map). The error map runs at parse time if a validation error occurs.
z.string({ error: () => `[${Date.now()}]: Validation failure.` });In Zod v3, there were separate params formessage(a string) anderrorMap(a function). These have been unified in Zod 4 aserror.
Context Object
The error map receives a context object (iss) with properties for customizing the error message based on the validation issue.
z.string({
error: (iss) =>
iss.input === undefined ? "Field is required." : "Invalid input.",
});Context Properties
z.string({
error: (iss) => {
iss.code; // the issue code
iss.input; // the input data
iss.inst; // the schema/check that originated this issue
iss.path; // the path of the error
},
});Schema-Specific Properties
Depending on the API, additional properties are available. Use TypeScript autocomplete to explore them.
z.string().min(5, {
error: (iss) => {
// ...the same as above
iss.minimum; // the minimum value
iss.inclusive; // whether the minimum is inclusive
return `Password must have ${iss.minimum} characters or more`;
},
});Returning undefined for Selective Customization
Return undefined to skip customization and fall back to the default message (the next error map in the precedence chain).
z.int64({
error: (issue) => {
// override too_big error message
if (issue.code === "too_big") {
return { message: `Value must be <${issue.maximum}` };
}
// defer to default
return undefined;
},
});Per-Parse Error Customization
Pass an error map into .parse() or .safeParse() to customize errors on a per-parse basis:
const schema = z.string();
schema.parse(12, {
error: (iss) => "per-parse custom error",
});Per-parse error maps have lower precedence than schema-level custom messages:
const schema = z.string({ error: "highest priority" });
const result = schema.safeParse(12, {
error: (iss) => "lower priority",
});
result.error.issues;
// [{ message: "highest priority", ... }]The iss object is a discriminated union of all possible issue types. Use the code property to discriminate between them:
const result = schema.safeParse(12, {
error: (iss) => {
if (iss.code === "invalid_type") {
return `invalid type, expected ${iss.expected}`;
}
if (iss.code === "too_small") {
return `minimum is ${iss.minimum}`;
}
// ...
},
});Include Input in Issues (reportInput)
By default, Zod does not include input data in issues to prevent unintentional logging of sensitive data. Use the reportInput flag to include it:
z.string().parse(12, {
reportInput: true,
});
// ZodError: [
// {
// "expected": "string",
// "code": "invalid_type",
// "input": 12, // included with reportInput
// "path": [],
// "message": "Invalid input: expected string, received number"
// }
// ]Global Error Customization
Use z.config() to set a global error map via the customError setting:
z.config({
customError: (iss) => {
return "globally modified error";
},
});Global error messages have lower precedence than schema-level or per-parse error messages.
Use code to discriminate issue types in a global error map:
z.config({
customError: (iss) => {
if (iss.code === "invalid_type") {
return `invalid type, expected ${iss.expected}`;
}
if (iss.code === "too_small") {
return `minimum is ${iss.minimum}`;
}
// ...
},
});Error Precedence
When multiple error customizations are defined, the following precedence applies (highest to lowest priority):
1. Schema-level error -- Any error message hard-coded into a schema definition.
z.string("Not a string!");2. Per-parse error -- A custom error map passed into .parse() or .safeParse().
z.string().parse(12, {
error: (iss) => "My custom error",
});3. Global error map -- A custom error map passed into z.config().
z.config({
customError: (iss) => "My custom error",
});4. Locale error map -- A locale loaded via z.config().
z.config(z.locales.en());Internationalization (i18n)
Zod provides built-in locales exported from zod/v4/core. The zod package loads the en locale automatically. Zod Mini does not load any locale by default (all error messages default to Invalid input).
Loading a Locale
import * as z from "zod";
import { en } from "zod/locales";
z.config(en());Lazy Loading with Dynamic Imports
import * as z from "zod";
async function loadLocale(locale: string) {
const { default: locale } = await import(`zod/v4/locales/${locale}.js`);
z.config(locale());
}
await loadLocale("fr");Using z.locales
All locales are exported as z.locales from "zod" (may not be tree-shakable in some bundlers):
import * as z from "zod";
z.config(z.locales.en());Available Locales
| Code | Language |
|---|---|
ar | Arabic |
az | Azerbaijani |
be | Belarusian |
bg | Bulgarian |
ca | Catalan |
cs | Czech |
da | Danish |
de | German |
en | English |
eo | Esperanto |
es | Spanish |
fa | Farsi |
fi | Finnish |
fr | French |
frCA | Canadian French |
he | Hebrew |
hu | Hungarian |
hy | Armenian |
id | Indonesian |
is | Icelandic |
it | Italian |
ja | Japanese |
ka | Georgian |
km | Khmer |
ko | Korean |
lt | Lithuanian |
mk | Macedonian |
ms | Malay |
nl | Dutch |
no | Norwegian |
ota | Ottoman Turkish |
ps | Pashto |
pl | Polish |
pt | Portuguese |
ru | Russian |
sl | Slovenian |
sv | Swedish |
ta | Tamil |
th | Thai |
tr | Turkish |
uk | Ukrainian |
ur | Urdu |
uz | Uzbek |
vi | Vietnamese |
yo | Yoruba |
zhCN | Simplified Chinese |
zhTW | Traditional Chinese |
Related
- Error Formatting
- Zod Core Issue Types
Error Formatting
Utilities for converting $ZodError instances into more useful formats for display, logging, and form validation.
Setup Example
All examples on this page use the following schema and invalid input:
import * as z from "zod";
const schema = z.strictObject({
username: z.string(),
favoriteNumbers: z.array(z.number()),
});
const result = schema.safeParse({
username: 1234,
favoriteNumbers: [1234, "4567"],
extraKey: 1234,
});
result.error!.issues;
// [
// {
// expected: 'string',
// code: 'invalid_type',
// path: [ 'username' ],
// message: 'Invalid input: expected string, received number'
// },
// {
// expected: 'number',
// code: 'invalid_type',
// path: [ 'favoriteNumbers', 1 ],
// message: 'Invalid input: expected number, received string'
// },
// {
// code: 'unrecognized_keys',
// keys: [ 'extraKey' ],
// path: [],
// message: 'Unrecognized key: "extraKey"'
// }
// ];z.treeifyError()
Converts a $ZodError into a nested object structure that mirrors the schema. Each node has an errors array, and special properties and items fields for traversing deeper into the tree.
const tree = z.treeifyError(result.error);
// =>
// {
// errors: [ 'Unrecognized key: "extraKey"' ],
// properties: {
// username: { errors: [ 'Invalid input: expected string, received number' ] },
// favoriteNumbers: {
// errors: [],
// items: [
// undefined,
// {
// errors: [ 'Invalid input: expected number, received string' ]
// }
// ]
// }
// }
// }Accessing Errors with Optional Chaining
Use optional chaining (?.) to safely access nested properties and avoid runtime errors when a path has no issues:
tree.properties?.username?.errors;
// => ["Invalid input: expected string, received number"]
tree.properties?.favoriteNumbers?.items?.[1]?.errors;
// => ["Invalid input: expected number, received string"];Be sure to use optional chaining (?.) to avoid errors when accessing nested properties.z.prettifyError()
Returns a human-readable string representation of the error, suitable for logging or CLI output.
const pretty = z.prettifyError(result.error);Output:
✖ Unrecognized key: "extraKey"
✖ Invalid input: expected string, received number
→ at username
✖ Invalid input: expected number, received string
→ at favoriteNumbers[1]z.formatError()
Deprecated -- Use z.treeifyError() instead.Converts a $ZodError into a nested object where each node contains an _errors array of error message strings. The structure mirrors the parsed data shape.
const formatted = z.formatError(result.error);
// =>
// {
// _errors: [ 'Unrecognized key: "extraKey"' ],
// username: {
// _errors: [ 'Invalid input: expected string, received number' ]
// },
// favoriteNumbers: {
// _errors: [],
// 1: {
// _errors: [ 'Invalid input: expected number, received string' ]
// }
// }
// }Access errors at a specific path:
formatted.username?._errors;
// => ["Invalid input: expected string, received number"]
formatted.favoriteNumbers?.[1]?._errors;
// => ["Invalid input: expected number, received string"]z.flattenError()
Converts a $ZodError into a shallow object with formErrors (top-level errors where path is []) and fieldErrors (per-field error arrays). Best suited for flat, one-level-deep schemas such as form validation.
const flattened = z.flattenError(result.error);
// { formErrors: string[], fieldErrors: { [key: string]: string[] } }
// =>
// {
// formErrors: [ 'Unrecognized key: "extraKey"' ],
// fieldErrors: {
// username: [ 'Invalid input: expected string, received number' ],
// favoriteNumbers: [ 'Invalid input: expected number, received string' ]
// }
// }Accessing Flattened Errors
flattened.fieldErrors.username;
// => [ 'Invalid input: expected string, received number' ]
flattened.fieldErrors.favoriteNumbers;
// => [ 'Invalid input: expected number, received string' ]Choosing a Formatter
| Utility | Best For | Structure |
|---|---|---|
z.treeifyError() | Deeply nested schemas, programmatic use | Nested tree with errors, properties, items |
z.prettifyError() | Logging, CLI output, debugging | Human-readable string |
z.flattenError() | Flat form validation | formErrors + fieldErrors |
z.formatError() | Legacy code (deprecated) | Nested _errors arrays |
Related
- Error Customization
- Defining Schemas
Zod — Errors
| Name | Description | Path |
|---|---|---|
| Error Customization | Customize validation error messages at the schema level, per-parse level, or globally… | ./error-customization.md |
| Error Formatting | Utilities for converting $ZodError instances into more useful formats for display… | ./error-formatting.md |
Basic Usage
Core concepts for defining schemas, parsing data, handling errors, and inferring types.
Defining a Schema
Before you can do anything else, you need to define a schema. Schemas describe the shape and constraints of your data.
import { z } from "zod";
const PlayerSchema = z.object({
username: z.string(),
xp: z.number(),
});Parsing Data
Given any Zod schema, use .parse to validate an input. If it is valid, Zod returns a strongly-typed _deep clone_ of the input.
const data = PlayerSchema.parse({ username: "billie", xp: 100 });
// => returns { username: "billie", xp: 100 }.parse(data)
Validates data against the schema. Returns the parsed (and possibly transformed) data on success. Throws a ZodError on failure.
PlayerSchema.parse({ username: "billie", xp: 100 });
// => { username: "billie", xp: 100 }
PlayerSchema.parse({ username: 42, xp: 100 });
// => throws ZodError.parseAsync(data)
Asynchronous version of .parse(). Returns a Promise that resolves with the parsed data or rejects with a ZodError. Required when using asynchronous refinements or transforms.
const result = await schema.parseAsync(data);Handling Errors
When validation fails, the .parse() method throws a ZodError instance with granular information about the validation issues.
try {
PlayerSchema.parse({ username: 42, xp: 100 });
} catch (error) {
if (error instanceof z.ZodError) {
error.issues;
/* [
{
code: 'invalid_type',
expected: 'string',
received: 'number',
path: [ 'username' ],
message: 'Expected string, received number',
}
] */
}
}.safeParse(data)
To avoid a try/catch block, use .safeParse() to get back a plain result object containing either the successfully parsed data or a ZodError. The result type is a discriminated union, so you can handle both cases conveniently.
const result = PlayerSchema.safeParse({ username: "billie", xp: 100 });
if (!result.success) {
result.error; // ZodError instance
} else {
result.data; // { username: string; xp: number }
}On success, result has the shape:
{ success: true; data: T }On failure:
{ success: false; error: ZodError }.safeParseAsync(data)
Asynchronous version of .safeParse(). Returns a Promise that resolves with the discriminated union result object. Required when using asynchronous refinements or transforms.
const result = await schema.safeParseAsync(data);
if (!result.success) {
result.error; // ZodError
} else {
result.data; // parsed value
}Inferring Types
Zod infers a static type from your schema definitions. You can extract this type with the z.infer<> utility and use it however you like.
z.infer<typeof schema>
Extracts the TypeScript output type from a schema.
import { z } from "zod";
const PlayerSchema = z.object({
username: z.string(),
xp: z.number(),
});
type Player = z.infer<typeof PlayerSchema>;
// => { username: string; xp: number }
// use it in your code
const player: Player = { username: "billie", xp: 100 };z.input<typeof schema> and z.output<typeof schema>
In some cases, the input and output types of a schema can diverge. For instance, the .transform() API can convert the input from one type to another. In these cases, you can extract the input and output types independently:
const schema = z.string().transform((val) => val.length);
type Input = z.input<typeof schema>;
// => string
type Output = z.output<typeof schema>;
// => numberNote:z.infer<>is an alias forz.output<>. They return the same type.
Related
- Introduction
Introduction
Zod is a TypeScript-first schema validation library with static type inference.
What is Zod
Zod lets you define _schemas_ to validate data, from a simple string to a complex nested object. When you parse data with a Zod schema, it validates the input and returns a strongly-typed, deep clone of the data you can use with confidence.
import { z } from "zod";
const User = z.object({
username: z.string(),
xp: z.number(),
});
// parse and validate unknown data
const data = User.parse({ username: "billie", xp: 100 });
// Zod infers the static type
// so you can use it with confidence :)
console.log(data.name);Features
- Zero external dependencies
- Works in Node.js and all modern browsers
- Tiny: 2kb core bundle (gzipped)
- Immutable API: methods return a new instance
- Concise interface
- Works with TypeScript and plain JS
- Built-in JSON Schema conversion
- Extensive ecosystem
Installation
Install via npm:
npm install zodZod is also available as @zod/zod on jsr.io:
npx jsr add @zod/zodRequirements
Zod is tested against TypeScript v5.5 and later. Older versions may work but are not officially supported.
Strict Mode
You must enable strict mode in your tsconfig.json. This is a best practice for all TypeScript projects.
// tsconfig.json
{
// ...
"compilerOptions": {
// ...
"strict": true
}
}Ecosystem
- tRPC - End-to-end typesafe APIs, with support for Zod schemas
- React Hook Form - Hook-based form validation with a Zod resolver
- zshy - Originally created as Zod's internal build tool. Bundler-free, batteries-included build tool for TypeScript libraries. Powered by
tsc.
Related
- Basic Usage
Getting Started
| Name | Description | Path |
|---|---|---|
| Basic Usage | Core concepts for defining schemas, parsing data, handling errors, and inferring types. | ./basic-usage.md |
| Introduction | Zod is a TypeScript-first schema validation library with static type inference. | ./introduction.md |
migration
| Name | Description | Path |
|---|---|---|
| Zod 4 Migration Guide | Complete reference for all breaking changes, deprecated APIs, and migration steps when upgrading from Zod 3 to Zod 4. | v4-migration-guide.md |
| Zod 4 Release Notes | New features, performance benchmarks, bundle size improvements, and architectural changes in Zod 4. | v4-release-notes.md |
Zod Mini
A tree-shakable variant of Zod that uses a functional API for significantly smaller bundle sizes.
Overview
Zod Mini implements the exact same functionality as zod, but using a functional, tree-shakable API. Methods are replaced with top-level functions that bundlers can eliminate when unused.
Installation
npm install zod@^4.0.0import * as z from "zod/mini";Functional vs Method API
In regular Zod, schemas expose chainable methods. In Zod Mini, you use functions and wrappers instead:
// regular Zod
const mySchema = z.string().optional().nullable();
// Zod Mini
const mySchema = z.nullable(z.optional(z.string()));// regular Zod
z.string().min(5).max(10).trim();
// Zod Mini
z.string().check(z.minLength(5), z.maxLength(10), z.trim());Tree-shaking Benefits
Tree-shaking (dead-code elimination) works on unused top-level functions but not on unused class methods. Zod Mini leverages this for significantly smaller bundles.
Bundle Size: Simple Schema
z.boolean().parse(true);| Package | Bundle size (gzip) |
|---|---|
| Zod Mini | 2.12kb |
| Zod | 5.91kb |
64% reduction with Zod Mini.
Bundle Size: Object Schema
const schema = z.object({ a: z.string(), b: z.number(), c: z.boolean() });
schema.parse({ a: "asdf", b: 123, c: true });| Package | Bundle size (gzip) |
|---|---|
| Zod Mini | 4.0kb |
| Zod | 13.1kb |
When (Not) to Use Zod Mini
Use regular Zod unless you have uncommonly strict bundle size constraints. Consider the following:
DX
The Zod Mini API is more verbose and less discoverable. Regular Zod methods are easier to discover and autocomplete through IntelliSense. Chained APIs are not available in Zod Mini.
Backend Development
Bundle size on the scale of Zod is not meaningful on the backend, even in resource-constrained environments like AWS Lambda:
| Bundle size | Lambda cold start time |
|---|---|
1kb | 171ms |
17kb (gzipped non-Mini Zod) | ~171.6ms (interpolated) |
128kb | 176ms |
The entirety of regular Zod gzipped is roughly 17kb, corresponding to a ~0.6ms increase in startup time.
Internet Speed
The round trip time to the server (100-200ms) dwarfs the time to download an additional 10kb. Only on slow 3G connections (sub-1Mbps) does this become significant. Unless optimizing specifically for users in rural or developing areas, bundle size at this scale is not the bottleneck.
ZodMiniType
All Zod Mini schemas extend z.ZodMiniType, which extends z.core.$ZodType from zod/v4/core. It implements fewer methods than ZodType in regular Zod.
Parsing
All Zod Mini schemas implement the same parsing methods as regular Zod:
import * as z from "zod/mini";
const mySchema = z.string();
mySchema.parse("asdf");
await mySchema.parseAsync("asdf");
mySchema.safeParse("asdf");
await mySchema.safeParseAsync("asdf");.check() Method
In Zod Mini, dedicated subclass methods (like .min(), .max()) are not available. Instead, pass checks into schemas using .check():
import * as z from "zod/mini";
z.string().check(
z.minLength(5),
z.maxLength(10),
z.refine((val) => val.includes("@")),
z.trim()
);Numeric Checks
z.lt(value); // less than
z.lte(value); // less than or equal (alias: z.maximum())
z.gt(value); // greater than
z.gte(value); // greater than or equal (alias: z.minimum())
z.positive(); // > 0
z.negative(); // < 0
z.nonpositive(); // <= 0
z.nonnegative(); // >= 0
z.multipleOf(value); // divisible by valueSize Checks (Arrays, Sets, Maps)
z.maxSize(value);
z.minSize(value);
z.size(value); // exact sizeString Checks
z.maxLength(value);
z.minLength(value);
z.length(value); // exact length
z.regex(regex);
z.lowercase();
z.uppercase();
z.includes(value);
z.startsWith(value);
z.endsWith(value);Object Checks
z.property(key, schema);Media Checks
z.mime(value);Custom Checks
z.refine((val) => val.length > 0); // custom refinement
z.check((val, ctx) => { // replaces .superRefine()
if (!isValid(val)) {
ctx.addIssue({ message: "Invalid" });
}
});Mutations
Mutations change the value without affecting the inferred type:
z.overwrite((value) => newValue); // arbitrary overwrite
z.normalize(); // Unicode NFC normalization
z.trim(); // trim whitespace
z.toLowerCase(); // convert to lowercase
z.toUpperCase(); // convert to uppercaseExample combining checks and mutations:
import * as z from "zod/mini";
const emailSchema = z.string().check(
z.trim(),
z.toLowerCase(),
z.minLength(5),
z.includes("@")
);Metadata
.register()
Register a schema in a registry:
const myReg = z.registry<{ title: string }>();
z.string().register(myReg, { title: "My cool string schema" });.meta() / .describe()
Attach metadata (registers in z.globalRegistry):
z.meta({ title: "...", description: "..." });
z.describe("...");.brand()
Brand a schema for nominal typing:
import * as z from "zod/mini";
const USD = z.string().brand("USD");.clone()
Returns an identical clone of the schema:
const mySchema = z.string();
mySchema.clone(mySchema._zod.def);No Default Locale
Unlike regular Zod, Zod Mini does not automatically load the English (en) locale. This reduces bundle size when error messages are unnecessary, localized to a non-English language, or otherwise customized.
By default, the message property of all issues will read "Invalid input". To load the English locale:
import * as z from "zod/mini";
z.config(z.locales.en());Related
- Zod
- Zod Core
packages
| Name | Description | Path |
|---|---|---|
| Zod Core (zod/v4/core) | The foundational sub-package that exports core classes and utilities consumed by Zod and Zod Mini -- not intended for direct use. | core.md |
| Zod Mini | A tree-shakable variant of Zod that uses a functional API for significantly smaller bundle sizes. | mini.md |
| Zod (zod/v4) | The flagship Zod package, balancing developer experience and bundle size for the vast majority of applications. | zod.md |
Basic Schema
Define a schema, parse data, and infer TypeScript types.
import { z } from "zod";
const PlayerSchema = z.object({
username: z.string(),
xp: z.number(),
});
// Parse validates and returns a strongly-typed deep clone
const data = PlayerSchema.parse({ username: "billie", xp: 100 });
// => { username: "billie", xp: 100 }
// Infer the TypeScript type from the schema
type Player = z.infer<typeof PlayerSchema>;
// => { username: string; xp: number }
const player: Player = { username: "billie", xp: 100 };Notes
.parse()throws aZodErroron validation failurez.infer<typeof Schema>extracts the static TypeScript type- All object properties are required by default
- Parsed result is a deep clone of the input, not the original reference