| name | zod |
| description | Zod 4 — TypeScript-first schema validation with static type inference. Use when writing Zod schemas, validating data, defining types with Zod, parsing input,
creating form validation schemas, defining API request/response schemas, working with z.object, z.string, z.number, z.enum, z.array, z.union, z.discriminatedUnion,
z.file, z.jwt, z.email, z.uuid, z.url, z.codec, z.toJSONSchema, z.fromJSONSchema, z.int, z.stringbool, z.templateLiteral, z.record, z.partialRecord,
or any other Zod API. Also use when migrating from Zod 3 to Zod 4, or when the user's package.json shows zod@^4.
CRITICAL: Always use Zod 4 APIs. Never use deprecated Zod 3 patterns unless user explicitly requests Zod 3 compatibility.
|
Zod 4
TypeScript-first schema validation. 2kb core bundle (gzipped). Zero dependencies.
CRITICAL: Zod 4 is the current stable version (zod@^4.0.0). Always write Zod 4 code. Never use deprecated Zod 3 patterns.
Import
import * as z from "zod";
For tree-shakable variant (smaller bundles):
import * as z from "zod/mini";
Core Patterns
Parsing
schema.parse(data);
schema.safeParse(data);
await schema.parseAsync(data);
await schema.safeParseAsync(data);
Type Inference
type MyType = z.infer<typeof mySchema>;
type MyInput = z.input<typeof mySchema>;
type MyOutput = z.output<typeof mySchema>;
Primitives
z.string();
z.number();
z.bigint();
z.boolean();
z.symbol();
z.undefined();
z.null();
z.date();
z.nan();
Coercion
z.coerce.string();
z.coerce.number();
z.coerce.boolean();
z.coerce.bigint();
z.coerce.date();
String Formats (Top-Level)
Use top-level functions, NOT methods. Methods like z.string().email() are deprecated.
z.email();
z.uuid();
z.url();
z.httpUrl();
z.hostname();
z.emoji();
z.base64();
z.base64url();
z.hex();
z.jwt();
z.nanoid();
z.cuid();
z.cuid2();
z.ulid();
z.ipv4();
z.ipv6();
z.mac();
z.cidrv4();
z.cidrv6();
z.hash("sha256");
z.e164();
z.iso.date();
z.iso.time();
z.iso.datetime();
z.iso.duration();
z.string().email();
z.string().uuid();
z.string().url();
UUID versions
z.uuid();
z.uuid({ version: "v4" });
z.uuidv4();
z.uuidv6();
z.uuidv7();
z.guid();
Custom email regex
z.email();
z.email({ pattern: z.regexes.html5Email });
z.email({ pattern: z.regexes.rfc5322Email });
z.email({ pattern: z.regexes.unicodeEmail });
JWTs
z.jwt();
z.jwt({ alg: "HS256" });
Numbers & Integers
z.number();
z.int();
z.int32();
z.float32();
z.float64();
z.bigint();
z.int64();
z.uint64();
Number validations
z.number().gt(5);
z.number().gte(5);
z.number().lt(5);
z.number().lte(5);
z.number().positive();
z.number().nonnegative();
z.number().negative();
z.number().nonpositive();
z.number().multipleOf(5);
String validations
z.string().max(5);
z.string().min(5);
z.string().length(5);
z.string().regex(/pattern/);
z.string().startsWith("abc");
z.string().endsWith("xyz");
z.string().includes("---");
z.string().uppercase();
z.string().lowercase();
z.string().trim();
z.string().toLowerCase();
z.string().toUpperCase();
z.string().normalize();
Objects
z.object({ name: z.string(), age: z.number() });
z.strictObject({ name: z.string() });
z.looseObject({ name: z.string() });
Deprecated: .strict(), .passthrough(), .strip(), .merge(), .deepPartial().
Object methods
schema.extend({ newField: z.string() });
schema.pick({ name: true });
schema.omit({ age: true });
schema.partial();
schema.partial({ name: true });
schema.required();
schema.keyof();
schema.shape.name;
Prefer spread syntax for best tsc performance:
z.object({ ...Base.shape, ...Extra.shape, newField: z.string() });
Recursive objects
const Category = z.object({
name: z.string(),
get subcategories() {
return z.array(Category);
},
});
Enums
z.enum(["A", "B", "C"]);
z.enum(MyTSEnum);
z.enum({ A: 0, B: 1 } as const);
Deprecated: z.nativeEnum(). Use z.enum() instead.
Arrays, Tuples, Sets, Maps
z.array(z.string());
z.array(z.string()).min(1).max(10).length(5);
z.tuple([z.string(), z.number()]);
z.tuple([z.string()], z.number());
z.set(z.number());
z.map(z.string(), z.number());
Unions & Intersections
z.union([z.string(), z.number()]);
z.discriminatedUnion("status", [
z.object({ status: z.literal("ok"), data: z.string() }),
z.object({ status: z.literal("err"), error: z.string() }),
]);
z.xor([z.string(), z.number()]);
z.intersection(schemaA, schemaB);
Records
z.record(z.string(), z.number());
z.record(z.enum(["a", "b"]), z.string());
z.partialRecord(z.enum(["a", "b"]), z.string());
z.looseRecord(z.string().regex(/^x_/), z.number());
Breaking: z.record(z.string()) single-arg form is removed. Always pass both key and value schemas.
Literals
z.literal("hello");
z.literal(42);
z.literal(true);
z.literal(["a", "b", "c"]);
Files
z.file();
z.file().min(10_000);
z.file().max(1_000_000);
z.file().mime("image/png");
z.file().mime(["image/png", "image/jpeg"]);
Stringbool
z.stringbool();
z.stringbool({ truthy: ["yes", "y"], falsy: ["no", "n"] });
z.stringbool({ case: "sensitive" });
Template Literals
z.templateLiteral(["hello, ", z.string()]);
z.templateLiteral([z.number(), z.enum(["px", "em", "rem"])]);
Optionals, Nullables, Defaults
z.optional(z.string());
z.nullable(z.string());
z.nullish(z.string());
z.string().default("hello");
z.string().prefault("hello");
z.number().catch(42);
Breaking: .default() now expects output type, not input type. Use .prefault() for old behavior.
Transforms & Pipes
z.transform((val) => String(val));
z.string().transform((val) => val.length);
z.preprocess((val) => String(val), z.string());
z.string().pipe(z.transform((val) => val.length));
z.number().overwrite((val) => val ** 2);
Codecs (Bidirectional Transforms)
New in Zod 4.1. Define encode/decode pairs:
const stringToDate = z.codec(z.iso.datetime(), z.date(), {
decode: (s) => new Date(s),
encode: (d) => d.toISOString(),
});
stringToDate.decode("2024-01-15T10:30:00.000Z");
stringToDate.encode(new Date());
stringToDate.parse("2024-01-15T10:30:00.000Z");
Error Customization
Use unified error param (replaces message, errorMap, invalid_type_error, required_error):
z.string().min(5, { error: "Too short" });
z.string({ error: (issue) => issue.input === undefined ? "Required" : "Not a string" });
z.string({ error: (issue) => {
if (issue.code === "too_small") return `Min ${issue.minimum}`;
}});
Deprecated: message, errorMap, invalid_type_error, required_error.
Metadata & Registries
z.string().meta({ id: "email", title: "Email", description: "User email" });
z.string().describe("A description");
const myReg = z.registry<{ description: string }>();
z.string().register(myReg, { description: "..." });
JSON Schema
z.toJSONSchema(schema);
z.toJSONSchema(schema, { target: "draft-07" });
z.toJSONSchema(schema, { target: "openapi-3.0" });
z.fromJSONSchema(jsonSchema);
Refinements
z.string().refine((val) => val.includes("@"), { error: "Must contain @" });
z.string().refine((val) => val.includes("@"), { error: "...", abort: true });
z.array(z.string()).superRefine((val, ctx) => {
ctx.addIssue({ code: "custom", message: "...", input: val });
});
Refinements now live inside schemas (not ZodEffects wrapper). You can interleave .refine() with other methods:
z.string().refine(v => v.includes("@")).min(5);
Error Pretty-Printing
z.prettifyError(zodError);
z.treeifyError(zodError);
Further Reference
- Zod Mini: See references/zod-mini.md for tree-shakable API differences,
.check() usage, and bundle size tradeoffs
- Codecs: See references/codecs.md for bidirectional transforms, encoding behavior, and common codec patterns (stringToDate, jsonCodec, etc.)
- JSON Schema: See references/json-schema.md for
z.toJSONSchema() options, format conversion, registry-based multi-schema, and z.fromJSONSchema()
- Advanced patterns: See references/advanced.md for registries, refinement
when, .superRefine(), .check(), functions, branded types, readonly, custom schemas, and advanced string/object/record/union options
- Migration from Zod 3: See references/migration.md for all breaking changes and deprecated APIs