Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Zod User Guide

Generated June 2026 from colinhacks/zod at bbc68f9

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Zod is a TypeScript-first schema declaration and validation library. You define a schema once — a description of the shape your data should have — and Zod gives you back two things from that single definition: a runtime validator that checks unknown data, and a static TypeScript type that you never have to write or maintain by hand.

This guide shows you how to install Zod, define schemas for every kind of data, parse and validate input, handle errors, transform values, and infer types. Everything here is derived directly from the Zod source code at the documented commit.

Note

This guide documents Zod 4 (package version 4.4.3), the current major version. Zod 4 is the default when you import { z } from "zod". Legacy Zod 3 remains available from the same package at zod/v3, and a tree-shakable variant called Zod Mini is available at zod/mini. See Zod Mini and Migrating between versions below.


Table of Contents

  1. Quick Start
  2. Installation
  3. Core Concepts
  4. Defining Schemas
  5. Parsing and Validation
  6. Customizing and Refining
  7. Transforms, Defaults, and Codecs
  8. Handling Errors
  9. Zod Mini
  10. Metadata, JSON Schema, and i18n
  11. Common Tasks
  12. Troubleshooting
  13. FAQ
  14. API Reference

1. Quick Start

Install Zod, define a schema, and parse some data:

npm install zod
import { z } from "zod";

// 1. Define a schema
const User = z.object({
  name: z.string(),
  age: z.number().int().positive(),
  email: z.string().email(),
});

// 2. Infer the static type (no manual interface needed)
type User = z.infer<typeof User>;
// { name: string; age: number; email: string }

// 3. Parse unknown data at runtime
const user = User.parse({
  name: "Alice",
  age: 30,
  email: "[email removed]",
});
// `user` is fully typed as User

// 4. Or validate without throwing
const result = User.safeParse({ name: "Bob", age: -1, email: "nope" });
if (!result.success) {
  console.log(result.error.issues);
} else {
  console.log(result.data);
}

That is the entire workflow: declare once, get a type and a validator. Every section below expands on a piece of this.

Tip

The z.infer<typeof Schema> trick is the heart of Zod. Define your validation logic once and let TypeScript derive the type — your runtime checks and your compile-time types can never drift apart.


2. Installation

Zod ships as a single dependency-free package on npm. Install it with your package manager of choice.

npm

npm install zod

pnpm

pnpm add zod

yarn

yarn add zod

bun

bun add zod

Deno (JSR)

deno add jsr:@zod/zod

Requirements

Requirement Detail
TypeScript 5.5 or later recommended (the repo builds and tests against ~5.5.4).
strict mode Required. Zod's type inference depends on "strict": true in your tsconfig.json.
Runtime Any modern JavaScript runtime — Node.js, Deno, Bun, browsers, edge/serverless. Zod has zero runtime dependencies.
Module system Ships both ESM and CommonJS builds; works with import and require.

Important

Enable strict mode in your tsconfig.json. Without it, Zod's inferred types silently degrade (for example, .optional() fields will not be correctly typed). This is the single most common setup mistake.

{
  "compilerOptions": {
    "strict": true
  }
}

Import styles

All of these are valid and equivalent for the main API:

import { z } from "zod";        // named import (most common)
import * as z from "zod";       // namespace import
import z from "zod";            // default import

The package also exposes several subpath entry points for specific use cases:

Import path What you get
zod Zod 4 classic API (the default).
zod/mini Zod Mini — a tree-shakable, function-based API.
zod/v3 The legacy Zod 3 API, unchanged, for incremental migration.
zod/v4 Zod 4 classic API, explicitly (identical to zod).
zod/v4/mini Zod 4 Mini, explicitly.
zod/v4/core The low-level core engine (for library authors — see the Admin Guide).
zod/v4/locales Built-in error-message translations.

3. Core Concepts

Three ideas underpin everything in Zod.

Schemas

A schema is an object that describes the expected shape of some data. You build schemas by calling factory functions on the z namespace and chaining methods:

const schema = z.string().min(3).max(20);

Every schema knows how to validate data against itself and carries two associated types: its input type and its output type (usually the same, but they differ when you use transforms — see Section 7).

Parsing

You apply a schema to data by parsing it. Parsing checks the data and either returns a typed, validated value or reports what went wrong. Zod gives you throwing (.parse) and non-throwing (.safeParse) variants, plus async forms for schemas that contain async logic.

Type inference

From any schema you can extract a static TypeScript type with the z.infer helper:

const Point = z.object({ x: z.number(), y: z.number() });
type Point = z.infer<typeof Point>; // { x: number; y: number }

You can also pull out the input and output types separately with z.input and z.output (these matter when a schema transforms its data):

type In = z.input<typeof Point>;
type Out = z.output<typeof Point>; // identical to z.infer

Note

z.infer is an alias for z.output. For schemas without transforms, input and output are the same type, so you rarely need to distinguish them.


4. Defining Schemas

This section catalogs every schema type Zod provides. Each is a function on the z namespace.

Primitives

z.string();
z.number();
z.boolean();
z.bigint();
z.symbol();
z.date();
z.undefined();
z.null();
z.void();
z.any();
z.unknown();
z.never();
z.nan();
z.file();
Schema Accepts
z.string() A string.
z.number() A finite number (rejects NaN).
z.boolean() A boolean.
z.bigint() A bigint.
z.symbol() A symbol.
z.date() A Date instance.
z.null() The value null.
z.undefined() The value undefined.
z.void() undefined (semantic alias for "no return value").
z.any() / z.unknown() Any value (the difference is the inferred TypeScript type).
z.never() Nothing — always fails.
z.nan() The value NaN.
z.file() A File instance (browser / Web file uploads).
const name = z.string();
name.parse("hello"); // → "hello"
name.parse(123);     // throws ZodError

String validations and formats

z.string() supports a rich set of chained checks and built-in format validators.

Length and content checks:

z.string().min(3);             // minimum length
z.string().max(20);            // maximum length
z.string().length(10);         // exact length
z.string().regex(/^[a-z]+$/);  // matches a pattern
z.string().includes("foo");    // contains a substring
z.string().startsWith("https://");
z.string().endsWith(".com");
z.string().lowercase();        // must be all lowercase
z.string().uppercase();        // must be all uppercase

Built-in string formats — each is available both as a standalone schema (z.email()) and, in many cases, as a method:

Format Schema Validates
Email z.email() An email address.
URL z.url() A URL.
UUID z.uuid() Any UUID. Version-specific: z.uuidv4(), z.uuidv6(), z.uuidv7().
GUID z.guid() A GUID.
Emoji z.emoji() A string of emoji.
Nano ID z.nanoid() A Nano ID.
CUID / CUID2 z.cuid(), z.cuid2() Collision-resistant IDs.
ULID z.ulid() A ULID.
XID / KSUID z.xid(), z.ksuid() Other sortable ID formats.
IPv4 / IPv6 z.ipv4(), z.ipv6() IP addresses.
MAC z.mac() A MAC address.
CIDR z.cidrv4(), z.cidrv6() CIDR network ranges.
Base64 z.base64(), z.base64url() Base64-encoded data.
E.164 z.e164() An international phone number.
JWT z.jwt() A JSON Web Token.

Warning

z.cuid() validates CUID v1, which its own authors have deprecated because timestamps are embedded in the id (an information-leak risk). Prefer z.cuid2() for new code.

ISO date/time formats live under the z.iso namespace:

z.iso.datetime();              // 2021-01-01T00:00:00Z
z.iso.datetime({ offset: true });   // allows timezone offsets (+07:00)
z.iso.datetime({ local: true });    // allows local times with no Z
z.iso.datetime({ precision: 3 });   // requires millisecond precision
z.iso.date();                  // 2021-01-01
z.iso.time();                  // 00:00:00
z.iso.time({ precision: 3 });  // 00:00:00.123
z.iso.duration();              // P3Y6M4DT12H30M5S

The following table shows exactly what z.iso.datetime() accepts under different options (verified against the test suite):

Input default {local: true} {offset: true} {precision: 3}
2021-01-01T00:00:00Z ✅ ✅ ✅ ❌
2021-01-01T00:00:00.123Z ✅ ✅ ✅ ✅
2021-01-01T00:00:00 (no zone) ❌ ✅ ❌ ❌
2021-01-01T00:00:00+07:00 ❌ ❌ ✅ ❌

Number validations and formats

z.number().min(0);          // alias: .gte(0)
z.number().max(100);        // alias: .lte(100)
z.number().gt(0);           // strictly greater than
z.number().lt(100);         // strictly less than
z.number().int();           // must be a safe integer
z.number().positive();      // > 0
z.number().negative();      // < 0
z.number().nonnegative();   // >= 0
z.number().nonpositive();   // <= 0
z.number().multipleOf(5);   // divisible by 5

Fixed-width numeric formats are also available as top-level schemas:

Schema Range / meaning
z.int() A safe integer (Number.isSafeInteger).
z.int32() / z.uint32() 32-bit signed / unsigned integer.
z.float32() / z.float64() 32-/64-bit float.
z.int64() / z.uint64() 64-bit bigint integers.

Objects

const User = z.object({
  name: z.string(),
  age: z.number(),
  email: z.string().email().optional(),
});

By default, unrecognized keys are stripped from the output. You can change that behavior:

Method / schema Unknown keys
z.object({...}) Stripped (default).
z.strictObject({...}) Rejected — parsing fails.
z.looseObject({...}) Passed through unchanged.
.catchall(schema) Validated against schema instead of stripped.

Object schemas have a powerful set of methods for deriving new schemas:

User.extend({ role: z.string() });    // add fields
User.pick({ name: true });            // keep only some fields
User.omit({ email: true });           // drop some fields
User.partial();                       // make all fields optional
User.required();                      // make all fields required
User.keyof();                         // a z.enum of the keys
User.shape;                           // access the field schemas

Note

.extend() and .partial() operate on a plain object schema. If you have added refinements to the object (see Section 6), use .safeExtend() instead — the source rejects key-manipulation methods on refined object schemas to prevent silently dropping your validation logic.

Arrays, tuples, and collections

z.array(z.string());                 // string[]
z.string().array();                  // equivalent, chained form
z.array(z.number()).min(1).max(10);  // length-constrained
z.tuple([z.string(), z.number()]);   // [string, number]
z.tuple([z.string()], z.boolean());  // [string, ...boolean[]] (rest element)
z.set(z.string());                   // Set<string>
z.map(z.string(), z.number());       // Map<string, number>
z.record(z.string(), z.number());    // Record<string, number>

Unions, intersections, and enums

z.union([z.string(), z.number()]);        // string | number
z.string().or(z.number());                // equivalent, chained form
z.intersection(schemaA, schemaB);         // A & B
schemaA.and(schemaB);                     // equivalent, chained form

z.enum(["red", "green", "blue"]);         // "red" | "green" | "blue"
z.enum(MyTypeScriptEnum);                 // from a native TS enum
z.literal("hello");                       // the exact value "hello"
z.literal(["a", "b"]);                    // "a" | "b"

For unions of objects that share a discriminator field, use z.discriminatedUnion — it is faster and produces clearer errors:

const Shape = z.discriminatedUnion("type", [
  z.object({ type: z.literal("circle"), radius: z.number() }),
  z.object({ type: z.literal("square"), side: z.number() }),
]);

Tip

Reach for z.discriminatedUnion whenever your union members are objects with a common literal "tag" field (like type or kind). Zod uses the discriminator to jump straight to the right member instead of trying each in turn, and the error message points at the actual mismatch rather than dumping every branch's failures.

Special and composite schemas

z.templateLiteral([z.string(), z.number()]);  // `${string}${number}`
z.lazy(() => z.string());                      // for recursive schemas
z.promise(z.string());                         // Promise<string>
z.json();                                       // any JSON-serializable value
z.instanceof(MyClass);                          // an instance of a class
z.custom<`${number}px`>((val) => /^\d+px$/.test(val as string));

z.lazy is how you model recursive types such as trees or linked lists:

const Category = z.object({
  name: z.string(),
  subcategories: z.lazy(() => z.array(Category)),
});

Optional, nullable, and nullish

z.string().optional();   // string | undefined
z.string().nullable();   // string | null
z.string().nullish();    // string | null | undefined
z.optional(z.string());  // standalone forms also exist
z.nullable(z.string());

Use .nonoptional() to remove a previously applied .optional().

Coercion

The z.coerce namespace builds schemas that convert their input before validating, using the corresponding JavaScript constructor:

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)
z.coerce.number().parse("42");   // → 42  (string coerced to number)
z.coerce.date().parse("2021-01-01T00:00:00Z"); // → Date

Caution

Coercion uses JavaScript's built-in conversion rules, which can surprise you. z.coerce.boolean() follows JS truthiness, so any non-empty string — including "false" — coerces to true. Only "", 0, null, and undefined coerce to false. If you need to parse the literal strings "true"/"false", write a custom transform instead.


5. Parsing and Validation

Once you have a schema, there are four ways to run it against data.

.parse() — throw on failure

const value = z.string().parse("hello"); // → "hello"
z.string().parse(123);                   // throws ZodError

Use .parse() when invalid data is a programming error you want to surface immediately (it throws a ZodError).

.safeParse() — return a result object

const result = z.string().safeParse(123);
if (result.success) {
  result.data; // the typed, validated value
} else {
  result.error; // a ZodError
}

.safeParse() never throws. It returns a discriminated union:

{ success: true;  data: T }
{ success: false; error: ZodError }

This is the right choice for validating external input (form submissions, API request bodies, environment variables) where failure is expected and you want to handle it gracefully.

Async parsing

If a schema contains an async refinement or transform, you must parse it asynchronously, otherwise Zod throws a synchronous-parse error:

const schema = z.string().refine(async (s) => await isUnique(s));

await schema.parseAsync("abc");      // throws ZodError on failure
await schema.safeParseAsync("abc");  // returns the result object
Method Throws? Returns Use when
.parse(data) Yes T Failure is a bug; you want an exception.
.safeParse(data) No { success, data | error } Failure is expected; handle it inline.
.parseAsync(data) Yes Promise<T> Schema has async logic.
.safeParseAsync(data) No Promise<{ success, ... }> Async logic, no throwing.

Important

A schema that contains any async refinement or transform cannot be run with the synchronous .parse() / .safeParse(). Attempting it throws: Encountered Promise during synchronous parse. Use .parseAsync() instead. Switch to the async variants.

Functional parsing

Every parse method is also available as a top-level function that takes the schema as its first argument. This form is the primary API in Zod Mini but works everywhere:

z.parse(schema, data);
z.safeParse(schema, data);
z.parseAsync(schema, data);
z.safeParseAsync(schema, data);

6. Customizing and Refining

When the built-in validations are not enough, add your own logic with refinements.

.refine() — custom predicate

const Password = z
  .string()
  .min(8)
  .refine((val) => /[A-Z]/.test(val), {
    message: "Must contain an uppercase letter",
  });

A refinement is a function that returns truthy for valid values. If it returns falsy, Zod records an issue. You can customize the message and the error path:

const Form = z
  .object({ password: z.string(), confirm: z.string() })
  .refine((data) => data.password === data.confirm, {
    message: "Passwords do not match",
    path: ["confirm"], // attach the error to the `confirm` field
  });

.superRefine() and .check() — multiple issues

For full control — emitting several issues, or inspecting the raw context — use .check() (Zod 4) with z.check, or the Zod 3-style .superRefine():

const schema = z.array(z.string()).check((ctx) => {
  if (ctx.value.length > 3) {
    ctx.issues.push({
      code: "custom",
      message: "Too many items",
      input: ctx.value,
    });
  }
});

Note

.with() is an alias for .check() and behaves identically — both attach a check to a schema. You can pass either a z.refine(...) / z.check(...) object or a plain context-taking function.

Custom error messages

Most schemas and checks accept an error message as a string argument or via an error option:

z.string("Name is required");
z.string().min(3, "Too short");
z.string().min(3, { error: "Too short" });

7. Transforms, Defaults, and Codecs

Schemas can change their data as it passes through, not just validate it.

.transform() — reshape the output

const schema = z.string().transform((val) => val.length);
schema.parse("hello"); // → 5  (a number!)

type In = z.input<typeof schema>;   // string
type Out = z.output<typeof schema>; // number

This is where input and output types diverge: the schema accepts a string but produces a number.

.pipe() — chain schemas

.pipe() feeds the output of one schema into another, letting you validate, transform, then validate again:

const schema = z
  .string()
  .transform((val) => val.length)
  .pipe(z.number().min(1));

schema.parse("hello"); // → 5
schema.parse("");      // throws — length 0 fails the .min(1)

.default() — supply a fallback for undefined

z.string().default("anonymous").parse(undefined); // → "anonymous"
z.string().default(() => generateId()).parse(undefined); // lazy default

.catch() — recover from any failure

z.number().catch(0).parse("not a number"); // → 0

// The catch callback receives the error context:
z.number().catch((ctx) => ctx.error.issues.length).parse("oops"); // → 1

Tip

The difference between .default() and .catch(): .default() only fires when the input is undefined; .catch() fires on any validation failure. Use .default() for "missing means X", and .catch() for "if anything is wrong, fall back to X".

.brand() — nominal typing

Branding makes two structurally identical types incompatible at the type level — useful for things like validated IDs:

const UserId = z.string().brand<"UserId">();
type UserId = z.infer<typeof UserId>; // string & { brand }

function load(id: UserId) {/* ... */}
load("raw string"); // ❌ compile error — must go through the schema first

.readonly() — freeze the output

const schema = z.object({ x: z.number() }).readonly();
// output type is Readonly<{ x: number }>, and Object.freeze is applied

Codecs — two-way transforms

Zod 4 introduces codecs: schemas that can run in both directions. Alongside the usual decode path (.parse, which goes input → output), you can encode to go output → input:

z.decode(schema, input);   // input → output (same as parse)
z.encode(schema, output);  // output → input (reverse)
z.decodeAsync(schema, input);
z.encodeAsync(schema, output);
z.safeDecode(schema, input);
z.safeEncode(schema, output);

Note

Encoding only works through bidirectional transforms. A one-way .transform() cannot be reversed; attempting to encode through one throws Encountered unidirectional transform during encode.


8. Handling Errors

When validation fails, Zod produces a ZodError.

The ZodError object

A ZodError carries an issues array. Each issue describes one validation failure:

const result = z.object({ name: z.string() }).safeParse({ name: 123 });
if (!result.success) {
  console.log(result.error.issues);
  // [
  //   {
  //     code: "invalid_type",
  //     expected: "string",
  //     path: ["name"],
  //     message: "Invalid input: expected string, received number"
  //   }
  // ]
}

Each issue has at least code, path (the location of the failure within the data), and message. The full set of issue codes:

Code Meaning
invalid_type Value is the wrong type (e.g. number where string expected).
too_big Exceeds a maximum length/size/value.
too_small Below a minimum length/size/value.
invalid_format A string-format check failed (email, url, regex, etc.).
not_multiple_of Number is not a multiple of the required divisor.
unrecognized_keys A strict object received unknown keys.
invalid_union No union member matched (or multiple matched ambiguously).
invalid_key A map/record key failed validation.
invalid_element A map/set element failed validation.
invalid_value Value is not one of the allowed literals/enum values.
custom A custom .refine() / .check() failed.

Formatting errors for display

Zod ships several helpers that reshape a ZodError into formats convenient for different surfaces:

import { z } from "zod";

const err = result.error;

z.prettifyError(err);  // a multi-line human-readable string
z.treeifyError(err);   // a nested tree mirroring your data shape
z.flattenError(err);   // { formErrors, fieldErrors } — great for forms
z.formatError(err);    // a nested object with _errors arrays

z.prettifyError produces output like:

✖ Invalid input: expected string, received number
  → at name

z.flattenError is ideal for form libraries — it separates top-level errors from per-field errors:

z.flattenError(err);
// {
//   formErrors: [],
//   fieldErrors: { name: ["Invalid input: expected string"] }
// }

Tip

For one-line-per-field form display, reach for z.flattenError. For arbitrarily nested data (objects within arrays within objects), z.treeifyError preserves the full structure so you can show an error next to each individual field.


9. Zod Mini

zod/mini is an alternative API surface backed by the same validation engine as classic Zod. It trades method chaining for standalone functions, which lets bundlers tree-shake away the parts of Zod you do not use — meaningfully reducing bundle size for size-sensitive apps (edge functions, browser bundles).

import * as z from "zod/mini";

const schema = z.object({
  name: z.string(),
  age: z.optional(z.number()),
});

// Validations are composed with z.* functions instead of chained methods:
const constrained = z.string().check(z.minLength(3), z.maxLength(20));

z.parse(schema, { name: "Alice" });

The mental mapping is: where classic Zod writes z.string().min(3).max(20), Zod Mini writes z.string().check(z.minLength(3), z.maxLength(20)). The schema types (z.string, z.object, etc.) are the same; the checks become composable function arguments.

Note

Use classic zod unless bundle size is a measured concern. The chained API is more ergonomic for most projects. Zod Mini exists specifically for cases where shaving kilobytes matters; both produce identical validation behavior because they share the core engine.


10. Metadata, JSON Schema, and i18n

Attaching metadata

You can annotate schemas with metadata — descriptions, titles, ids — without affecting validation. The simplest method is .describe():

const Name = z.string().describe("The user's full name");

For richer metadata, register schemas with the global registry or your own registry:

const Email = z.string().email().meta({
  id: "email",
  title: "Email address",
  description: "A valid email",
  deprecated: false,
});

// Or use a standalone registry:
const myRegistry = z.registry();
myRegistry.add(Email, { id: "email" });

Metadata fields (id, title, description, deprecated, plus any custom keys) are primarily consumed when generating JSON Schema. Metadata is inherited from parent schemas, except for id, which is never inherited.

Generating JSON Schema

Convert any schema to a JSON Schema document:

const schema = z.object({ name: z.string(), age: z.number() });
const jsonSchema = z.toJSONSchema(schema);

Registered metadata flows into the output (titles, descriptions, ids become $defs references).

Warning

Not every Zod type has a JSON Schema equivalent. Converting a schema that contains a bigint, symbol, Date, Map, Set, function, transform, or custom type throws a descriptive error (for example, Date cannot be represented in JSON Schema). Keep schemas you intend to export to JSON Schema within representable types, or strip the offending parts first.

Internationalized error messages

Zod includes error-message translations for over 45 languages under zod/v4/locales. Activate one globally with z.config:

import { z } from "zod";
import fr from "zod/v4/locales/fr.js";

z.config(fr()); // all error messages now in French

After this, a type error on a French-configured schema reads, for example, Entrée invalide : chaîne attendu, nombre reçu instead of the English default.

Available locales include ar, de, en, es, fr, it, ja, ko, pt, ru, tr, uk, zh-CN, zh-TW, and many more (50+ in total).


11. Common Tasks

Practical, copy-paste recipes for things you will do often.

Validate an API request body

const CreateUser = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().min(0).optional(),
});

app.post("/users", (req, res) => {
  const result = CreateUser.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json(z.flattenError(result.error));
  }
  createUser(result.data); // fully typed
});

Parse and validate environment variables

const Env = z.object({
  NODE_ENV: z.enum(["development", "production", "test"]),
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
});

export const env = Env.parse(process.env);

Tip

Use z.coerce.number() for env vars — process.env values are always strings, and coercion converts "3000" into the number 3000 for you. Pair it with .default() to supply sensible fallbacks.

Model a recursive (tree) structure

const TreeNode = z.object({
  value: z.string(),
  children: z.lazy(() => z.array(TreeNode)),
});

Reuse and derive schemas

const Base = z.object({ id: z.string(), createdAt: z.date() });

const Post = Base.extend({ title: z.string(), body: z.string() });
const PostUpdate = Post.partial();        // every field optional
const PostPreview = Post.pick({ title: true, id: true });

Validate a discriminated message format

const Event = z.discriminatedUnion("type", [
  z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
  z.object({ type: z.literal("key"), key: z.string() }),
]);

const e = Event.parse(incoming); // narrowed by `type` after parsing

12. Troubleshooting

Error: Expected string, received number

Cause: The data you parsed does not match the schema's type — here, a number was supplied where the schema expects a string.

Solution: Inspect error.issues to find the exact path. Either fix the input or, if the conversion is intentional, use z.coerce.string() to convert automatically.

Prevention: Validate at the boundary of your system (API handlers, form submit) with .safeParse, and surface field-level errors to the caller.

Error: Encountered Promise during synchronous parse. Use .parseAsync() instead.

Cause: Your schema contains an async refinement or transform, but you called the synchronous .parse() / .safeParse().

Solution: Switch to .parseAsync() or .safeParseAsync() and await the result.

Prevention: Any schema with async (val) => ... logic anywhere inside it is async end-to-end. Track which schemas are async and always parse them with the async methods.

Error: z.int is not a function (or Cannot read properties of undefined)

Cause: A bundler or shared package pulled in a mismatched or partial copy of Zod — typically when a library bundles Zod 4 and your app also imports Zod, leading to two incompatible instances (reported in community issue #6018).

Solution: Ensure a single version of Zod is installed (deduplicate with your package manager). If a dependency bundles Zod, align its major version with yours.

Prevention: Keep Zod as a single, shared dependency rather than letting libraries bundle their own copy.

Error: Date cannot be represented in JSON Schema

Cause: You called z.toJSONSchema() on a schema containing a type with no JSON Schema equivalent (z.date(), z.bigint(), z.symbol(), z.map(), z.set(), a transform, or a custom type).

Solution: Remove or replace the unrepresentable type before converting. For dates, validate ISO strings with z.iso.datetime() instead of z.date() when JSON Schema export matters.

Prevention: Design schemas intended for JSON Schema export using only JSON-representable types.

Content Security Policy error: unsafe-eval

Cause: Zod 4 compiles validators just-in-time using the Function constructor for speed. A strict CSP that forbids unsafe-eval blocks this (community issue #4461).

Solution: Enable Zod's JIT-less mode, which disables compilation:

z.config({ jitless: true });

Prevention: In CSP-restricted environments (some browser extensions, locked-down web apps), set jitless: true at startup before defining schemas.

.optional() fields are not typed as optional

Cause: TypeScript strict mode is not enabled. Zod's inference relies on it.

Solution: Set "strict": true in your tsconfig.json.

Prevention: Always run Zod under strict mode — it is a hard requirement, not a recommendation.

Validation passes but extra object keys disappear

Cause: z.object() strips unknown keys by default — this is intended behavior, not a bug.

Solution: Use z.looseObject() to pass unknown keys through, z.strictObject() to reject them, or .catchall(schema) to validate them.

Prevention: Choose the object variant that matches your intent up front.


13. FAQ

Q: What is the difference between .parse() and .safeParse()? .parse() throws a ZodError on failure and returns the value on success. .safeParse() never throws; it returns { success: true, data } or { success: false, error }. Use .safeParse() for untrusted input you expect might be invalid.

Q: How do I get a TypeScript type from a schema? Use z.infer<typeof Schema>. For schemas with transforms, use z.input<typeof Schema> and z.output<typeof Schema> to get the pre- and post-transform types.

Q: Do I have to install any other dependencies? No. Zod has zero runtime dependencies. You only need TypeScript (5.5+ recommended) with strict mode for the best inference.

Q: What is Zod Mini and when should I use it? zod/mini is a function-based, tree-shakable API backed by the same engine. Use it only when bundle size is a measured concern; otherwise the classic chained API is more ergonomic.

Q: Can I still use Zod 3? Yes. The legacy Zod 3 API is bundled and available at zod/v3 with no changes, so you can migrate incrementally.

Q: How do I make all fields of an object optional? Call .partial() on the object schema. To make them required again, use .required().

Q: How do I add a custom validation rule? Use .refine(predicate, message) for a single check, or .check() / .superRefine() when you need to emit multiple issues or inspect the validation context.

Q: How do I validate that two fields match (e.g. password confirmation)? Apply .refine() to the object schema and use the path option to attach the error to the relevant field. See Section 6.

Q: My union errors are huge and hard to read. What can I do? If your union members are objects sharing a literal tag field, switch to z.discriminatedUnion(key, [...]). It produces a single targeted error instead of collecting every branch's failures.

Q: How do I change the language of error messages? Import a locale from zod/v4/locales and pass it to z.config(), e.g. z.config(z.locales.fr()).

Q: How do I turn a schema into JSON Schema? Call z.toJSONSchema(schema). Note that some Zod types (dates, bigints, maps, sets, transforms) cannot be represented and will throw.

Q: Does coercion handle "false" correctly for booleans? No — z.coerce.boolean() uses JavaScript truthiness, so "false" becomes true. Use a custom transform if you need string-aware boolean parsing.

Q: How do I provide a default value? .default(value) supplies a fallback when the input is undefined. Use .catch(value) to fall back on any validation failure.

Q: What is a codec? A codec is a Zod 4 schema that runs in both directions. z.decode (≡ parse) goes input → output; z.encode reverses it through bidirectional transforms.


14. API Reference

Schema factories

Category Functions
Primitives string, number, boolean, bigint, symbol, date, undefined, null, void, any, unknown, never, nan, file
String formats email, url, uuid, uuidv4, uuidv6, uuidv7, guid, emoji, nanoid, cuid, cuid2, ulid, xid, ksuid, ipv4, ipv6, mac, cidrv4, cidrv6, base64, base64url, e164, jwt
ISO formats iso.datetime, iso.date, iso.time, iso.duration
Numeric formats int, int32, uint32, float32, float64, int64, uint64
Coercion coerce.string, coerce.number, coerce.boolean, coerce.bigint, coerce.date
Objects object, strictObject, looseObject, record
Collections array, tuple, set, map
Compositions union, discriminatedUnion, intersection, enum, nativeEnum, literal, templateLiteral
Special lazy, promise, json, instanceof, custom, optional, nullable, nullish, nonoptional
Transforms transform, preprocess, pipe

Schema methods (chained)

Method Purpose
.optional() / .nullable() / .nullish() Wrap as optional/nullable.
.default(v) / .catch(v) Fallback for undefined / any failure.
.refine(fn, msg) / .check(fn) / .superRefine(fn) Custom validation.
.transform(fn) / .pipe(schema) Reshape / chain output.
.brand<T>() / .readonly() Nominal typing / freeze output.
.array() / .or(s) / .and(s) Build array / union / intersection.
.min / .max / .length / .regex / etc. Type-specific checks.
.meta(obj) / .describe(text) Attach metadata.
.parse / .safeParse / .parseAsync / .safeParseAsync Run validation.

Object-schema methods

.extend, .safeExtend, .pick, .omit, .partial, .required, .keyof, .catchall, .shape

Parsing functions (top-level)

z.parse, z.safeParse, z.parseAsync, z.safeParseAsync, z.decode, z.encode, z.decodeAsync, z.encodeAsync, z.safeDecode, z.safeEncode

Error utilities

z.prettifyError, z.treeifyError, z.flattenError, z.formatError, z.toDotPath

Type helpers

z.infer, z.input, z.output

Configuration & metadata

z.config({ customError, localeError, jitless }), z.registry(), z.globalRegistry, z.toJSONSchema(schema), z.locales.*