# Zod

Zod is a TypeScript-first schema validation library with static type inference. This documentation provides comprehensive coverage of Zod 4's features, API, and usage patterns.

# Defining schemas

import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
import { Callout } from "fumadocs-ui/components/callout"
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';

To validate data, you must first define a *schema*. Schemas represent *types*, from simple primitive values to complex nested objects and arrays.

## Primitives

```ts
import * as z from "zod";

// primitive types
z.string();
z.number();
z.bigint();
z.boolean();
z.symbol();
z.undefined();
z.null();
```

### Coercion

To coerce input data to the appropriate type, use `z.coerce` instead:

```ts
z.coerce.string();    // String(input)
z.coerce.number();    // Number(input)
z.coerce.boolean();   // Boolean(input)
z.coerce.bigint();    // BigInt(input)
```

The coerced variant of these schemas attempts to convert the input value to the appropriate type.

```ts
const schema = z.coerce.string();

schema.parse("tuna");    // => "tuna"
schema.parse(42);        // => "42"
schema.parse(true);      // => "true"
schema.parse(null);      // => "null"
```

The input type of these coerced schemas is `unknown` by default. To specify a more specific input type, pass a generic parameter:

```ts
const A = z.coerce.number();
type AInput = z.input<typeof A>; // => unknown

const B = z.coerce.number<number>();
type BInput = z.input<typeof B>; // => number
```

<Accordions type="single">
  <Accordion title="How coercion works in Zod">
    Zod coerces all inputs using the built-in constructors.

    | 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)` |

    Boolean coercion with `z.coerce.boolean()` may not work how you expect. Any [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) value is coerced to `true`, and any [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) value is coerced to `false`.

    ```ts
    const schema = z.coerce.boolean(); // Boolean(input)

    schema.parse("tuna"); // => true
    schema.parse("true"); // => true
    schema.parse("false"); // => true
    schema.parse(1); // => true
    schema.parse([]); // => true

    schema.parse(0); // => false
    schema.parse(""); // => false
    schema.parse(undefined); // => false
    schema.parse(null); // => false
    ```

    For total control over coercion logic, consider using [`z.transform()`](#transforms) or [`z.pipe()`](#pipes).
  </Accordion>

  <Accordion title="Customizing the input type">
    By default the *input* type of any `z.coerce` schema is `unknown`. In some cases, it may be preferable for the input type to be more specific. You can specify the input type with a generic parameter.

    ```ts
    const regularCoerce = z.coerce.string();
    type RegularInput = z.input<typeof regularCoerce>; // => unknown
    type RegularOutput = z.output<typeof regularCoerce>; // => string

    const customInput = z.coerce.string<string>();
    type CustomInput = z.input<typeof customInput>; // => string
    type CustomOutput = z.output<typeof customInput>; // => string
    ```
  </Accordion>
</Accordions>

## Literals

Literal schemas represent a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types), like `"hello world"` or `5`.

```ts
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`:

```ts
z.null();
z.undefined();
z.void(); // equivalent to z.undefined()
```

To allow multiple literal values:

```ts
const colors = z.literal(["red", "green", "blue"]);

colors.parse("green"); // ✅
colors.parse("yellow"); // ❌
```

To extract the set of allowed values from a literal schema:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    colors.values; // => Set<"red" | "green" | "blue">
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    // no equivalent
    ```
  </Tab>
</Tabs>

## Strings

{/* Zod provides a handful of built-in string validation and transform APIs.

  <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
  ```ts
  z.string().startsWith("fourscore")
  ```
  </Tab>
  <Tab value="Zod Mini">
  ```ts
  z.string().check(z.startsWith("fourscore"))
  ```
  </Tab>
  </Tabs>

  All of the APIs documented below support the `error` parameter for customizing the error message.

  <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
  ```ts
  z.string().startsWith("fourscore", {error: "Nice try, buddy"})
  ```
  </Tab>
  <Tab value="Zod Mini">
  ```ts
  z.string().check(z.startsWith("fourscore", {error: "Nice try, buddy"}))
  ```
  </Tab></Tabs> */}

Zod provides a handful of built-in string validation and transform APIs. To perform some common string validations:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    z.string().max(5);
    z.string().min(5);
    z.string().length(5);
    z.string().nonempty(); // alias for .min(1)
    z.string().regex(/^[a-z]+$/);
    z.string().startsWith("aaa");
    z.string().endsWith("zzz");
    z.string().includes("---");
    z.string().uppercase();
    z.string().lowercase();
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.string().check(z.maxLength(5));
    z.string().check(z.minLength(5));
    z.string().check(z.length(5));
    z.string().check(z.minLength(1)); // alias for .nonempty()
    z.string().check(z.regex(/^[a-z]+$/));
    z.string().check(z.startsWith("aaa"));
    z.string().check(z.endsWith("zzz"));
    z.string().check(z.includes("---"));
    z.string().check(z.uppercase());
    z.string().check(z.lowercase());
    ```
  </Tab>
</Tabs>

Length is measured in Unicode code points, not UTF-16 code units. Emoji outside the Basic Multilingual Plane count as one, and combining marks and ZWJ sequences count as several.

```ts
z.string().length(1).parse("😀"); // one code point, two UTF-16 units
z.string().length(2).parse("e\u0301"); // "é" — an e plus a combining acute
z.string().length(3).parse("🧑‍🍼"); // person + ZWJ + baby bottle
```

To perform some simple string transforms:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    z.string().trim(); // trim whitespace
    z.string().toLowerCase(); // toLowerCase
    z.string().toUpperCase(); // toUpperCase
    z.string().normalize(); // normalize unicode characters
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.string().check(z.trim()); // trim whitespace
    z.string().check(z.toLowerCase()); // toLowerCase
    z.string().check(z.toUpperCase()); // toUpperCase
    z.string().check(z.normalize()); // normalize unicode characters
    ```
  </Tab>
</Tabs>

## String formats

To validate against some common string formats:

```ts
z.email();
z.uuid();
z.url();
z.httpUrl();       // http or https URLs only
z.hostname();
z.e164();          // E.164 phone numbers
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.creditCard();    // credit card number (Luhn checksum)
z.currencyCode();  // ISO 4217 currency code
z.iban();          // IBAN (ISO 7064 mod-97 checksum)
z.hash("sha256");  // or "sha1", "sha384", "sha512", "md5"
z.iso.date();
z.iso.time();
z.iso.datetime();
z.iso.duration();
```

### Emails

To validate email addresses:

```ts
z.email();
```

By default, Zod uses a comparatively strict email regex designed to validate normal email addresses containing common characters. It's roughly equivalent to the rules enforced by Gmail. To learn more about this regex, refer to [this post](https://colinhacks.com/essays/reasonable-email-regex).

```ts
/^(?!\.)(?!.*\.\.)([a-z0-9_'+\-\.]*)[a-z0-9_+-]@([a-z0-9][a-z0-9\-]*\.)+[a-z]{2,}$/i
```

To customize the email validation behavior, you can pass a custom regular expression to the `pattern` param.

```ts
z.email({ pattern: /your regex here/ });
```

Zod exports several useful regexes you could use.

```ts
// Zod's default email regex
z.email();
z.email({ pattern: z.regexes.email }); // equivalent

// the regex used by browsers to validate input[type=email] fields
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email
z.email({ pattern: z.regexes.html5Email });

// the classic emailregex.com regex (RFC 5322)
z.email({ pattern: z.regexes.rfc5322Email });

// a loose regex that allows Unicode (good for intl emails)
z.email({ pattern: z.regexes.unicodeEmail });
```

### UUIDs

To validate UUIDs:

```ts
z.uuid();
```

To specify a particular UUID version:

```ts
// supports "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"
z.uuid({ version: "v4" });

// for convenience
z.uuidv4();
z.uuidv6();
z.uuidv7();
```

The RFC 9562/4122 UUID spec requires the first two bits of byte 8 to be `10`. Other UUID-like identifiers do not enforce this constraint. To validate any UUID-like identifier:

```ts
z.guid();
```

### URLs

To validate any WHATWG-compatible URL:

```ts
const schema = z.url();

schema.parse("https://example.com"); // ✅
schema.parse("http://localhost"); // ✅
schema.parse("mailto:noreply@zod.dev"); // ✅
```

As you can see this is quite permissive. Internally this uses the `new URL()` constructor to validate inputs; this behavior may differ across platforms and runtimes but it's the mostly rigorous way to validate URIs/URLs on any given JS runtime/engine.

To validate the hostname against a specific regex:

```ts
const schema = z.url({ hostname: /^example\.com$/ });

schema.parse("https://example.com"); // ✅
schema.parse("https://zombo.com"); // ❌
```

To validate the protocol against a specific regex, use the `protocol` param.

```ts
const schema = z.url({ protocol: /^https$/ });

schema.parse("https://example.com"); // ✅
schema.parse("http://example.com"); // ❌
```

<Callout>
  **Web URLs** — In many cases, you'll want to validate Web URLs specifically. Use `z.httpUrl()`:

  ```ts
  z.httpUrl();

  // equivalent to
  z.url({
    protocol: /^https?$/,
    hostname: z.regexes.domain
  });
  ```

  This restricts the protocol to `http`/`https` and ensures the hostname is a valid domain name with the `z.regexes.domain` regular expression:

  ```ts
  /^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/
  ```
</Callout>

To normalize URLs, use the `normalize` flag. This will overwrite the input value with the [normalized URL](https://chatgpt.com/share/6881547f-bebc-800f-9093-f5981e277c2c) returned by `new URL()`.

```ts
new URL("HTTP://ExAmPle.com:80/./a/../b?X=1#f oo").href
// => "http://example.com/b?X=1#f%20oo"
```

### Phone numbers

To validate phone numbers in E.164 format:

```ts
const phone = z.e164();

phone.parse("+15555555555"); // ✅
phone.parse("555-555-5555"); // ❌
```

This schema validates strings with a leading `+`, a non-zero country code, and 7 to 15 digits total.

### ISO datetimes

As you may have noticed, Zod string includes a few date/time related validations. These validations are regular expression based, so they are not as strict as a full date/time library. However, they are very convenient for validating user input.

The `z.iso.datetime()` method accepts a strict subset of ISO 8601; by default, no timezone offsets are allowed:

```ts
const datetime = z.iso.datetime();

datetime.parse("2020-01-01T06:15:00Z"); // ✅
datetime.parse("2020-01-01T06:15:00.123Z"); // ✅
datetime.parse("2020-01-01T06:15:00.123456Z"); // ✅ (arbitrary precision)
datetime.parse("2020-01-01T06:15:00+02:00"); // ❌ (offsets not allowed)
datetime.parse("2020-01-01T06:15:00"); // ❌ (local not allowed)
```

To allow timezone offsets:

```ts
const datetime = z.iso.datetime({ offset: true });

// allows timezone offsets
datetime.parse("2020-01-01T06:15:00+02:00"); // ✅

// basic offsets not allowed
datetime.parse("2020-01-01T06:15:00+02");    // ❌
datetime.parse("2020-01-01T06:15:00+0200");  // ❌

// Z is still supported
datetime.parse("2020-01-01T06:15:00Z"); // ✅ 
```

To allow unqualified (timezone-less) datetimes:

```ts
const schema = z.iso.datetime({ local: true });
schema.parse("2020-01-01T06:15:01"); // ✅
schema.parse("2020-01-01T06:15"); // ✅ seconds optional
schema.parse("2020-01-01T06:15:00Z"); // ✅
schema.parse("2020-01-01T06:15Z"); // ❌ (a `Z` requires seconds)
```

To constrain the allowable time `precision`. By default, seconds are required and sub-second precision is arbitrary. RFC 3339 mandates the seconds wherever a `Z` or an offset is present, so only the unqualified form `local` adds may omit them.

```ts
const a = z.iso.datetime();
a.parse("2020-01-01T06:15Z"); // ❌ (seconds required)
a.parse("2020-01-01T06:15:00Z"); // ✅
a.parse("2020-01-01T06:15:00.123Z"); // ✅

const b = z.iso.datetime({ precision: -1 }); // minute precision (no seconds)
b.parse("2020-01-01T06:15Z"); // ✅
b.parse("2020-01-01T06:15:00Z"); // ❌
b.parse("2020-01-01T06:15:00.123Z"); // ❌

const c = z.iso.datetime({ precision: 0 }); // second precision only
c.parse("2020-01-01T06:15Z"); // ❌
c.parse("2020-01-01T06:15:00Z"); // ✅
c.parse("2020-01-01T06:15:00.123Z"); // ❌

const d = z.iso.datetime({ precision: 3 }); // millisecond precision only
d.parse("2020-01-01T06:15Z"); // ❌
d.parse("2020-01-01T06:15:00Z"); // ❌
d.parse("2020-01-01T06:15:00.123Z"); // ✅
```

To accept minute precision alongside the rest, union the two — no single `precision` covers both.

```ts
const mixed = z.union([z.iso.datetime(), z.iso.datetime({ precision: -1 })]);
mixed.parse("2020-01-01T06:15Z"); // ✅
mixed.parse("2020-01-01T06:15:00.123Z"); // ✅
mixed.parse("2020-01-01T06:15"); // ❌ (still qualified-only)
```

### ISO dates

The `z.iso.date()` method validates strings in the format `YYYY-MM-DD`.

```ts
const date = z.iso.date();

date.parse("2020-01-01"); // ✅
date.parse("2020-1-1"); // ❌
date.parse("2020-01-32"); // ❌
```

### ISO times

The `z.iso.time()` method validates strings in the format `HH:MM[:SS[.s+]]`. By default seconds are optional, as are sub-second decimals.

```ts
const time = z.iso.time();

time.parse("03:15"); // ✅
time.parse("03:15:00"); // ✅
time.parse("03:15:00.9999999"); // ✅ (arbitrary precision)
```

No offsets of any kind are allowed.

```ts
time.parse("03:15:00Z"); // ❌ (no `Z` allowed)
time.parse("03:15:00+02:00"); // ❌ (no offsets allowed)
```

Use the `precision` parameter to constrain the allowable decimal precision.

```ts
z.iso.time({ precision: -1 }); // HH:MM (minute precision)
z.iso.time({ precision: 0 });  // HH:MM:SS (second precision)
z.iso.time({ precision: 1 });  // HH:MM:SS.s (decisecond precision)
z.iso.time({ precision: 2 });  // HH:MM:SS.ss (centisecond precision)
z.iso.time({ precision: 3 });  // HH:MM:SS.sss (millisecond precision)
```

### IP addresses

```ts
const ipv4 = z.ipv4();
ipv4.parse("192.168.0.0"); // ✅

const ipv6 = z.ipv6();
ipv6.parse("2001:db8:85a3::8a2e:370:7334"); // ✅
```

### IP blocks (CIDR)

Validate IP address ranges specified with [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing).

```ts
const cidrv4 = z.cidrv4();
cidrv4.parse("192.168.0.0/24"); // ✅

const cidrv6 = z.cidrv6();
cidrv6.parse("2001:db8::/32"); // ✅
```

### MAC Addresses

Validate standard 48-bit MAC address [IEEE 802](https://en.wikipedia.org/wiki/MAC_address).

```ts
const mac = z.mac(); 
mac.parse("00:1A:2B:3C:4D:5E");  // ✅
mac.parse("00-1a-2b-3c-4d-5e");  // ❌ colon-delimited by default
mac.parse("001A:2B3C:4D5E");     // ❌ standard formats only
mac.parse("00:1A:2b:3C:4d:5E");  // ❌ no mixed case

// custom delimiter
const dashMac = z.mac({ delimiter: "-" });
dashMac.parse("00-1A-2B-3C-4D-5E"); // ✅
```

### Credit card numbers

Validate a card number: 12 to 19 digits with a valid [Luhn](https://en.wikipedia.org/wiki/Luhn_algorithm) checksum. The issuer is not identified, so cards from any scheme are accepted.

```ts
const card = z.creditCard();
card.parse("4111111111111111");     // ✅
card.parse("4111 1111 1111 1111");  // ✅ single spaces
card.parse("4111-1111-1111-1111");  // ✅ single hyphens
card.parse("4111  1111 1111 1111"); // ❌ no repeated separators
card.parse(" 4111111111111111");    // ❌ no surrounding whitespace
card.parse("4111.1111.1111.1111");  // ❌ spaces and hyphens only
card.parse("4111111111111112");     // ❌ failed checksum
```

### Currency codes

The `z.currencyCode()` method validates three-letter ISO 4217 currency codes.

```ts
const currency = z.currencyCode();

currency.parse("USD"); // ✅
currency.parse("usd"); // ❌
currency.parse("ANG"); // ❌ withdrawn from ISO 4217
```

### IBANs

Validate an International Bank Account Number (IBAN): 2-letter country code, 2 check digits, and 11 to 30 alphanumeric characters with a valid [ISO 7064 MOD 97-10](https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN) checksum. The country registry is not consulted, so any country code is accepted and no country's own account length is enforced. Electronic format only.

```ts
const iban = z.iban();
iban.parse("DE89370400440532013000");      // ✅
iban.parse("NO9386011117947");             // ✅
iban.parse("DE89 3704 0044 0532 0130 00"); // ❌ no spaces
iban.parse("de89370400440532013000");      // ❌ uppercase only
iban.parse("DE89370400440532013001");      // ❌ failed checksum
iban.parse("DE00000000000000000066");      // ❌ check digits are always 02–98
```

### JWTs

Validate [JSON Web Tokens](https://jwt.io/).

```ts
z.jwt();
z.jwt({ alg: "HS256" });
```

### Hashes

To validate cryptographic hash values:

```ts
z.hash("md5");
z.hash("sha1");
z.hash("sha256");
z.hash("sha384");
z.hash("sha512");
```

By default, `z.hash()` expects hexadecimal encoding, as is conventional. You can specify a different encoding with the `enc` parameter:

```ts
z.hash("sha256", { enc: "hex" });       // default
z.hash("sha256", { enc: "base64" });    // base64 encoding
z.hash("sha256", { enc: "base64url" }); // base64url encoding (no padding)
```

<Accordions>
  <Accordion title="Expected lengths and padding">
    | Algorithm / Encoding | `"hex"` | `"base64"`      | `"base64url"` |
    | -------------------- | ------- | --------------- | ------------- |
    | `"md5"`              | 32      | 24 (22 + "==")  | 22            |
    | `"sha1"`             | 40      | 28 (27 + "=")   | 27            |
    | `"sha256"`           | 64      | 44 (43 + "=")   | 43            |
    | `"sha384"`           | 96      | 64 (no padding) | 64            |
    | `"sha512"`           | 128     | 88 (86 + "==")  | 86            |
  </Accordion>
</Accordions>

### Custom formats

To define your own string formats:

```ts
const coolId = z.stringFormat("cool-id", (val)=>{
  // arbitrary validation here
  return val.length === 100 && val.startsWith("cool-");
});

// a regex is also accepted
z.stringFormat("cool-id", /^cool-[a-z0-9]{95}$/);
```

This schema will produce `"invalid_format"` issues, which are more descriptive than the `"custom"` errors produced by refinements or `z.custom()`.

```ts
myFormat.parse("invalid input!");
// ZodError: [
//   {
//     "code": "invalid_format",
//     "format": "cool-id",
//     "path": [],
//     "message": "Invalid cool-id"
//   }
// ]
```

## Template literals

> Introduced in `zod@4.0`.

To define a template literal schema:

```ts
const schema = z.templateLiteral([ "hello, ", z.string(), "!" ]);
// `hello, ${string}!`
```

The `z.templateLiteral` API can handle any number of string literals (e.g. `"hello"`) and schemas. Any schema with an inferred type that's assignable to `string | number | bigint | boolean | null | undefined` can be passed.

```ts
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`
```

## Numbers

Use `z.number()` to validate numbers. It allows any finite number.

```ts
const schema = z.number();

schema.parse(3.14);      // ✅
schema.parse(NaN);       // ❌
schema.parse(Infinity);  // ❌
```

Zod implements a handful of number-specific validations:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    z.number().gt(5);
    z.number().gte(5);                     // alias .min(5)
    z.number().lt(5);
    z.number().lte(5);                     // alias .max(5)
    z.number().positive();                 // alias .gt(0)
    z.number().nonnegative();    
    z.number().negative(); 
    z.number().nonpositive(); 
    z.number().multipleOf(5);              // alias .step(5)
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.number().check(z.gt(5));
    z.number().check(z.gte(5));            // alias .minimum(5)
    z.number().check(z.lt(5));
    z.number().check(z.lte(5));            // alias .maximum(5)
    z.number().check(z.positive());        // alias .gt(0)
    z.number().check(z.nonnegative()); 
    z.number().check(z.negative()); 
    z.number().check(z.nonpositive()); 
    z.number().check(z.multipleOf(5));     // alias .step(5)
    ```
  </Tab>
</Tabs>

If (for some reason) you want to validate `NaN`, use `z.nan()`.

```ts
z.nan().parse(NaN);              // ✅
z.nan().parse("anything else");  // ❌
```

## Integers

To validate integers:

```ts
z.int();     // restricts to safe integer range
z.int32();   // restrict to int32 range
```

## BigInts

To validate BigInts:

```ts
z.bigint();
```

Zod includes a handful of bigint-specific validations.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    z.bigint().gt(5n);
    z.bigint().gte(5n);                    // alias `.min(5n)`
    z.bigint().lt(5n);
    z.bigint().lte(5n);                    // alias `.max(5n)`
    z.bigint().positive();                 // alias `.gt(0n)`
    z.bigint().nonnegative(); 
    z.bigint().negative(); 
    z.bigint().nonpositive(); 
    z.bigint().multipleOf(5n);             // alias `.step(5n)`
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.bigint().check(z.gt(5n));
    z.bigint().check(z.gte(5n));           // alias `.minimum(5n)`
    z.bigint().check(z.lt(5n));
    z.bigint().check(z.lte(5n));           // alias `.maximum(5n)`
    z.bigint().check(z.positive());        // alias `.gt(0n)`
    z.bigint().check(z.nonnegative());
    z.bigint().check(z.negative());
    z.bigint().check(z.nonpositive());
    z.bigint().check(z.multipleOf(5n));    // alias `.step(5n)`
    ```
  </Tab>
</Tabs>

## Booleans

To validate boolean values:

```ts
z.boolean().parse(true); // => true
z.boolean().parse(false); // => false
```

## Dates

Use `z.date()` to validate `Date` instances.

```ts
z.date().safeParse(new Date()); // success: true
z.date().safeParse("2022-01-12T06:15:00.000Z"); // success: false
```

To customize the error message:

```ts
z.date({
  error: issue => issue.input === undefined ? "Required" : "Invalid date"
});
```

Zod provides a handful of date-specific validations.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    z.date().min(new Date("1900-01-01"), { error: "Too old!" });
    z.date().max(new Date(), { error: "Too young!" });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.date().check(z.minimum(new Date("1900-01-01"), { error: "Too old!" }));
    z.date().check(z.maximum(new Date(), { error: "Too young!" }));
    ```
  </Tab>
</Tabs>

<div id="zod-enums" style={{height:"0px" }} />

## Enums

Use `z.enum` to validate inputs against a fixed set of allowable *string* values.

```ts
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);

FishEnum.parse("Salmon"); // => "Salmon"
FishEnum.parse("Swordfish"); // => ❌
```

<Callout>
  Careful — If you declare your string array as a variable, Zod won't be able to properly infer the exact values of each element.

  ```ts
  const fish = ["Salmon", "Tuna", "Trout"];

  const FishEnum = z.enum(fish);
  type FishEnum = z.infer<typeof FishEnum>; // string
  ```

  To fix this, always pass the array directly into the `z.enum()` function, or use [`as const`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions).

  ```ts
  const fish = ["Salmon", "Tuna", "Trout"] as const;

  const FishEnum = z.enum(fish);
  type FishEnum = z.infer<typeof FishEnum>; // "Salmon" | "Tuna" | "Trout"
  ```
</Callout>

Enum-like object literals (`{ [key: string]: string | number }`) are supported.

```ts
const Fish = {
  Salmon: 0,
  Tuna: 1
} as const

const FishEnum = z.enum(Fish)
FishEnum.parse(Fish.Salmon); // => ✅
FishEnum.parse(0); // => ✅
FishEnum.parse(2); // => ❌
```

You can also pass in an externally-declared TypeScript enum.

```ts
enum Fish {
  Salmon = 0,
  Tuna = 1
}

const FishEnum = z.enum(Fish);
FishEnum.parse(Fish.Salmon); // => ✅
FishEnum.parse(0); // => ✅
FishEnum.parse(2); // => ❌
```

<Callout>
  Use `z.enum()` for externally declared TypeScript enums. The `z.nativeEnum()` API is deprecated.

  Note that using TypeScript's `enum` keyword is [not recommended](https://www.totaltypescript.com/why-i-dont-like-typescript-enums).
</Callout>

```ts
enum Fish {
  Salmon = "Salmon",
  Tuna = "Tuna",
  Trout = "Trout",
}

const FishEnum = z.enum(Fish);
```

### `.enum`

To extract the schema's values as an enum-like object:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);

    FishEnum.enum;
    // => { Salmon: "Salmon", Tuna: "Tuna", Trout: "Trout" }
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);

    FishEnum.def.entries;
    // => { Salmon: "Salmon", Tuna: "Tuna", Trout: "Trout" }
    ```
  </Tab>
</Tabs>

### `.exclude()`

To create a new enum schema, excluding certain values:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
    const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]);
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    // no equivalent
     
    ```
  </Tab>
</Tabs>

### `.extract()`

To create a new enum schema, extracting certain values:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
    const SalmonAndTroutOnly = FishEnum.extract(["Salmon", "Trout"]);
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    // no equivalent
     
    ```
  </Tab>
</Tabs>

## Stringbools \[#stringbool]

> Introduced in `zod@4.0`.

In some cases (e.g. parsing environment variables) it's valuable to parse certain string "boolish" values to a plain `boolean` value. Use `z.stringbool()` for this:

```ts
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 */); // ZodError<[{ code: "invalid_value" }]>
```

To customize the truthy and falsy values:

```ts
// these are the defaults
z.stringbool({
  truthy: ["true", "1", "yes", "on", "y", "enabled"],
  falsy: ["false", "0", "no", "off", "n", "disabled"],
});
```

By default the schema is *case-insensitive*; all inputs are converted to lowercase before comparison to the `truthy`/`falsy` values. To make it case-sensitive:

```ts
z.stringbool({
  case: "sensitive"
});
```

## Optionals

To make a schema *optional* (that is, to allow `undefined` inputs).

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    z.optional(z.literal("yoda")); // or z.literal("yoda").optional()
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.optional(z.literal("yoda"));
    ```
  </Tab>
</Tabs>

This returns a `ZodOptional` instance that wraps the original schema. To extract the inner schema:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    optionalYoda.unwrap(); // ZodLiteral<"yoda">
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    optionalYoda.def.innerType; // ZodMiniLiteral<"yoda">
    ```
  </Tab>
</Tabs>

<Callout>
  **Exact optionals** — To allow an absent key without allowing an explicit `undefined`, use `.exactOptional()`, per TypeScript's [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes).

  <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
    <Tab value="Zod">
      ```ts
      const User = z.object({
        name: z.string().optional(),
        nick: z.string().exactOptional(),
      });
      // { name?: string | undefined; nick?: string }

      User.parse({ name: undefined }); // ✅
      User.parse({ nick: undefined }); // ❌
      ```
    </Tab>

    <Tab value="Zod Mini">
      ```ts
      const User = z.object({
        name: z.optional(z.string()),
        nick: z.exactOptional(z.string()),
      });
      // { name?: string | undefined; nick?: string }

      z.parse(User, { name: undefined }); // ✅
      z.parse(User, { nick: undefined }); // ❌
      ```
    </Tab>
  </Tabs>
</Callout>

## Nullables

To make a schema *nullable* (that is, to allow `null` inputs).

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    z.nullable(z.literal("yoda")); // or z.literal("yoda").nullable()
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const nullableYoda = z.nullable(z.literal("yoda"));
    ```
  </Tab>
</Tabs>

This returns a `ZodNullable` instance that wraps the original schema. To extract the inner schema:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    nullableYoda.unwrap(); // ZodLiteral<"yoda">
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    nullableYoda.def.innerType; // ZodMiniLiteral<"yoda">
    ```
  </Tab>
</Tabs>

## Nullish

To make a schema *nullish* (both optional and nullable):

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const nullishYoda = z.nullish(z.literal("yoda"));
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const nullishYoda = z.nullish(z.literal("yoda"));
    ```
  </Tab>
</Tabs>

Refer to the TypeScript manual for more about the concept of [nullish](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing).

## Unknown

Zod aims to mirror TypeScript's type system one-to-one. As such, Zod provides APIs to represent the following special types:

```ts
// allows any values
z.any(); // inferred type: `any`
z.unknown(); // inferred type: `unknown`
```

As object properties, their keys are required, matching `{ a: any }` in TypeScript.

```ts
z.object({ a: z.any() }).parse({}); // ❌
z.object({ a: z.any() }).parse({ a: undefined }); // ✅
z.object({ a: z.any().optional() }).parse({}); // ✅
```

## Never

No value will pass validation.

```ts
z.never(); // inferred type: `never`
```

## Objects

To define an object type:

```ts z.object
  // all properties are required by default
  const Person = z.object({
    name: z.string(),
    age: z.number(),
  });

  type Person = z.infer<typeof Person>;
  // => { name: string; age: number; }
```

By default, all properties are required. To make certain properties optional:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts z.object
    const Dog = z.object({
      name: z.string(),
      age: z.number().optional(),
    });

    Dog.parse({ name: "Yeller" }); // ✅
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts z.object
    const Dog = z.object({
      name: z.string(),
      age: z.optional(z.number())
    });

    Dog.parse({ name: "Yeller" }); // ✅
    ```
  </Tab>
</Tabs>

By default, unrecognized keys are *stripped* from the parsed result:

```ts z.object
Dog.parse({ name: "Yeller", extraKey: true });
// => { name: "Yeller" }
```

### `z.strictObject`

To define a *strict* schema that throws an error when unknown keys are found:

```ts z.object
const StrictDog = z.strictObject({
  name: z.string(),
});

StrictDog.parse({ name: "Yeller", extraKey: true });
// ❌ throws
```

### `z.looseObject`

To define a *loose* schema that allows unknown keys to pass through:

```ts z.object
const LooseDog = z.looseObject({
  name: z.string(),
});

LooseDog.parse({ name: "Yeller", extraKey: true });
// => { name: "Yeller", extraKey: true }
```

### `.catchall()`

To define a *catchall schema* that will be used to validate any unrecognized keys:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts z.object
    const DogWithStrings = z
      .object({
        name: z.string(),
        age: z.number().optional(),
      })
      .catchall(z.string());


    DogWithStrings.parse({ name: "Yeller", extraKey: "extraValue" }); // ✅
    DogWithStrings.parse({ name: "Yeller", extraKey: 42 }); // ❌
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts z.object
    const DogWithStrings = z.catchall(
      z.object({
        name: z.string(),
        age: z.number().optional(),
      }),
      z.string()
    );

    DogWithStrings.parse({ name: "Yeller", extraKey: "extraValue" }); // ✅
    DogWithStrings.parse({ name: "Yeller", extraKey: 42 }); // ❌
    ```
  </Tab>
</Tabs>

### `.shape`

To access the internal schemas:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    Dog.shape.name; // => string schema
    Dog.shape.age; // => number schema
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    Dog.def.shape.name; // => string schema
    Dog.def.shape.age; // => number schema
    ```
  </Tab>
</Tabs>

### `.keyof()`

To create a `ZodEnum` schema from the keys of an object schema:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const keySchema = Dog.keyof();
    // => ZodEnum<{ name: "name"; age: "age" }>
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const keySchema = z.keyof(Dog);
    // => ZodEnum<{ name: "name"; age: "age" }>
    ```
  </Tab>
</Tabs>

### `.extend()`

To add additional fields to an object schema:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const DogWithBreed = Dog.extend({
      breed: z.string(),
    });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const DogWithBreed = z.extend(Dog, {
      breed: z.string(),
    });
    ```
  </Tab>
</Tabs>

This API can be used to overwrite existing fields! Be careful with this power! If the two schemas share keys, B will override A.

<Callout>
  **Alternative: spread syntax** — You can alternatively avoid `.extend()` altogether by creating a new object schema entirely. This makes the strictness level of the resulting schema visually obvious.

  ```ts
  const DogWithBreed = z.object({ // or z.strictObject() or z.looseObject()...
    ...Dog.shape,
    breed: z.string(),
  });
  ```

  You can also use this to merge multiple objects in one go.

  ```ts
  const DogWithBreed = z.object({
    ...Animal.shape,
    ...Pet.shape,
    breed: z.string(),
  });
  ```

  This approach has a few advantages:

  1. It uses language-level features ([spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)) instead of library-specific APIs
  2. The same syntax works in Zod and Zod Mini
  3. It's more `tsc`-efficient — the `.extend()` method can be expensive on large schemas, and due to [a TypeScript limitation](https://github.com/microsoft/TypeScript/pull/61505) it gets quadratically more expensive when calls are chained
  4. If you wish, you can change the strictness level of the resulting schema by using `z.strictObject()` or `z.looseObject()`
</Callout>

### `.safeExtend()`

The `.safeExtend()` method works similarly to `.extend()`, but it won't let you overwrite an existing property with a non-assignable schema. In other words, the result of `.safeExtend()` will have an inferred type that [`extends`](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#conditional-type-constraints) the original (in the TypeScript sense).

```ts
z.object({ a: z.string() }).safeExtend({ a: z.string().min(5) }); // ✅
z.object({ a: z.string() }).safeExtend({ a: z.any() }); // ✅
z.object({ a: z.string() }).safeExtend({ a: z.number() });
//                                       ^  ❌ ZodNumber is not assignable 
```

Use `.safeExtend()` to extend schemas that contain refinements. (Regular `.extend()` will throw an error when used on schemas with refinements.)

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    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)
    });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const Base = z.object({
      a: z.string(),
      b: z.string()
    }).check(z.refine(user => user.a === user.b));

    // Extended inherits the refinements of Base
    const Extended = z.safeExtend(Base, {
      a: z.string().min(10)
    });
    ```
  </Tab>
</Tabs>

### `.pick()`

Inspired by TypeScript's built-in `Pick` and `Omit` utility types, Zod provides dedicated APIs for picking and omitting certain keys from an object schema.

Starting from this initial schema:

```ts z.object
const Recipe = z.object({
  title: z.string(),
  description: z.string().optional(),
  ingredients: z.array(z.string()),
});
// { title: string; description?: string | undefined; ingredients: string[] }
```

To pick certain keys:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts zod
    const JustTheTitle = Recipe.pick({ title: true });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const JustTheTitle = z.pick(Recipe, { title: true });
    ```
  </Tab>
</Tabs>

### `.omit()`

To omit certain keys:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts zod
    const RecipeNoId = Recipe.omit({ id: true });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const RecipeNoId = z.omit(Recipe, { id: true });
    ```
  </Tab>
</Tabs>

### `.partial()`

For convenience, Zod provides a dedicated API for making some or all properties optional, inspired by the built-in TypeScript utility type [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype).

To make all fields optional:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts zod
    const PartialRecipe = Recipe.partial();
    // { title?: string | undefined; description?: string | undefined; ingredients?: string[] | undefined }
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const PartialRecipe = z.partial(Recipe);
    // { title?: string | undefined; description?: string | undefined; ingredients?: string[] | undefined }
    ```
  </Tab>
</Tabs>

To make certain properties optional:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts zod
    const RecipeOptionalIngredients = Recipe.partial({
      ingredients: true,
    });
    // { title: string; description?: string | undefined; ingredients?: string[] | undefined }
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const RecipeOptionalIngredients = z.partial(Recipe, {
      ingredients: true,
    });
    // { title: string; description?: string | undefined; ingredients?: string[] | undefined }
    ```
  </Tab>
</Tabs>

### `.exactPartial()`

Identical to `.partial()`, but wraps each field in [`exactOptional()`](#optionals) instead of `optional()`:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts zod
    const PartialRecipe = Recipe.exactPartial();
    // { title?: string; description?: string | undefined; ingredients?: string[] }
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const PartialRecipe = z.exactPartial(Recipe);
    // { title?: string; description?: string | undefined; ingredients?: string[] }
    ```
  </Tab>
</Tabs>

### `z.deepPartial()` \[#deep-partial]

Where `.partial()` only touches the top-level shape, `z.deepPartial()` recurses to every object in the tree — through arrays, tuples, unions, records, and wrappers.

```ts
const Post = z.object({
  title: z.string(),
  author: z.object({ name: z.string(), email: z.string() }),
});

z.deepPartial(Post).parse({ author: {} }); // ✅
```

The source schema is never modified, and the result is still a `ZodObject`, so `.shape` and `.extend()` keep working. A [discriminated union](#discriminated-unions) degrades to a plain `z.union()`, since an optional discriminator defeats the discriminated lookup. Like `.partial()`, it throws on an object carrying its own [refinement](#refinements).

### `.required()`

Zod provides an API for making some or all properties *required*, inspired by TypeScript's [`Required`](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) utility type.

To make all properties required:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts zod
    const RequiredRecipe = Recipe.required();
    // { title: string; description: string; ingredients: string[] }
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const RequiredRecipe = z.required(Recipe);
    // { title: string; description: string; ingredients: string[] }
    ```
  </Tab>
</Tabs>

To make certain properties required:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts zod
    const RecipeRequiredDescription = Recipe.required({description: true});
    // { title: string; description: string; ingredients: string[] }
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const RecipeRequiredDescription = z.required(Recipe, {description: true});
    // { title: string; description: string; ingredients: string[] }
    ```
  </Tab>
</Tabs>

### Symbol keys

A shape can declare a symbol key. A `const` symbol infers as `unique symbol`, so the key is tracked in the inferred type and its value is validated like any other:

```ts
const TAG = Symbol("tag");
const Post = z.object({ title: z.string(), [TAG]: z.number() });
// { title: string; [TAG]: number }

Post.parse({ title: "hello", [TAG]: 42 }); // ✅
Post.parse({ title: "hello" });            // ❌ the symbol key is required
```

Declared symbol keys work with `.partial()`, `.pick()`, `.omit()`, `.extend()`, and the rest. Symbol keys the shape doesn't declare are ignored — `z.looseObject()` won't pass them through, and `z.strictObject()` won't flag them.

## Recursive objects

To define a self-referential type, use a [getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) on the key. This lets JavaScript resolve the cyclical schema at runtime.

```ts
const Category = z.object({
  name: z.string(),
  get subcategories(){
    return z.array(Category)
  }
});

type Category = z.infer<typeof Category>;
// { name: string; subcategories: Category[] }
```

Cyclical inputs work out of the box with Zod. For bundle size reasons, Zod Mini requires you to register a memoizer explicitly (see code example).

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const input: any = { name: "root", subcategories: [] };
    input.subcategories.push(input);

    const result = Category.parse(input);
    result.subcategories[0] === result; // true

    // the output graph mirrors the input graph
    result.subcategories[0].subcategories[0] === result; // true
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    // Zod Mini requires a memoizer, registered before schemas are defined
    z.config({ memoizer: z.memoizer() });

    const input: any = { name: "root", subcategories: [] };
    input.subcategories.push(input);

    const result = Category.parse(input);
    result.subcategories[0] === result; // true
    ```
  </Tab>
</Tabs>

You can also represent *mutually recursive types*:

```ts
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 as you'd expect.

### Circularity errors

Due to TypeScript limitations, recursive type inference can be finicky, and it only works in certain scenarios. Some more complicated types may trigger recursive type errors like this:

```ts
const Activity = z.object({
  name: z.string(),
  get subactivities() {
    // ^ ❌ 'subactivities' implicitly has return type 'any' because it does not
    // have a return type annotation and is referenced directly or indirectly
    // in one of its return expressions.ts(7023)

    return z.nullable(z.array(Activity));
  },
});
```

In these cases, you can resolve the error with a type annotation on the offending getter:

```ts
const Activity = z.object({
  name: z.string(),
  get subactivities(): z.ZodNullable<z.ZodArray<typeof Activity>> {
    return z.nullable(z.array(Activity));
  },
});
```

{/* Some general rules of thumb for avoiding circularity  */}

{/* 
  <Accordions>
  <Accordion title="Resolving type errors in recursive schemas">
    Recursive type inference can by mysterious. TypeScript is capable of it in certain limited scenarios. Depending on what you're trying to do, you may encounter errors like this:

    ```ts
    export const Activity = z.object({
      name: z.string(),
      get children() {
        // ^ ❌ 'children' implicitly has return type 'any' because it does not 
        // have a return type annotation and is referenced directly or indirectly 
        // in one of its return expressions.ts(7023)

        return z.optional(z.array(Activity)); //.optional();
      },
    });
    ```

    Here are a couple rules of thumb:

    ### Object types only 
    
    Generally speaking, recursive inference only works with object types that are referencing each other. TypeScript has special handling for resolving getter-based recursive objects, which is what Zod relies on. If you try to add non-object types into the mix, you'll likely encounter errors.

    ```ts
    const Activity = z.object({
      name: z.string(),
      get children() { // ❌ type error
        return z.optional(ActivityArray);
      },
    });

    const ActivityArray = z.array(Activity);
    ```

    Sometimes you can get around this limitation by defining 


    ### Avoid nesting function calls
    
    Functions like `z.array()` and `z.optional()` accept Zod schemas, so when you use them TypeScript will do some type-checking on their inputs to make sure they are valid. But type checking is the enemy of recursive type inference—it's hard for TypeScript to *check* and *infer* types at the same time. Methods do not have this problem, so prefer methods over functions when possible (sorry Zod Mini users).

    ```ts
    const Activity = z.object({
      name: z.string(),
      get subactivities() {
        // ^ ❌ 'subactivities' implicitly has return type 'any' because it does not 
        // have a return type annotation and is referenced directly or indirectly 
        // in one of its return expressions.ts(7023)

        return z.union([z.null(), Activity]);
      },
    });
    ```

    ### Fall back to type annotations on your getters
    
    When in doubt, you can generally sidestep these issues with some carefully deployed type annotations on your getters. Due to the limitations described above, this is particularly necessary when using Zod Mini.

    ```ts
    import * as z from "zod";

    const Activity = z.object({
      name: z.string(),
      get subactivities(): z.ZodMiniDefault<z.ZodMiniArray<typeof Activity>> {
        return z._default(z.array(Activity), []);
      },
    });
    ```

  </Accordion>
  </Accordions> */}

## Arrays

To define an array schema:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const stringArray = z.array(z.string()); // or z.string().array()
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const stringArray = z.array(z.string());
    ```
  </Tab>
</Tabs>

To access the inner schema for an element of the array.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    stringArray.unwrap(); // => string schema
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    stringArray.def.element; // => string schema
    ```
  </Tab>
</Tabs>

Zod implements a number of array-specific validations:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    z.array(z.string()).nonempty(); // must contain at least 1 item
    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 5 items exactly
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.array(z.string()).check(z.minLength(1)); // alias for .nonempty()
    z.array(z.string()).check(z.minLength(5)); // must contain 5 or more items
    z.array(z.string()).check(z.maxLength(5)); // must contain 5 or fewer items
    z.array(z.string()).check(z.length(5)); // must contain 5 items exactly
    ```
  </Tab>
</Tabs>

{/* Unlike `.nonempty()` these methods do not change the inferred type. */}

## Tuples

Unlike arrays, tuples are typically fixed-length arrays that specify different schemas for each index.

```ts
const MyTuple = z.tuple([
  z.string(),
  z.number(),
  z.boolean()
]);

type MyTuple = z.infer<typeof MyTuple>;
// [string, number, boolean]
```

To add a variadic ("rest") argument:

```ts
const variadicTuple = z.tuple([z.string()], z.number());
// => [string, ...number[]];
```

To make every element optional:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts zod
    const PartialTuple = z.tuple([z.string(), z.number()]).partial();
    // => [(string | undefined)?, (number | undefined)?]

    z.tuple([z.string()], z.number()).partial();
    // => [(string | undefined)?, ...number[]]
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const PartialTuple = z.partial(z.tuple([z.string(), z.number()]));
    // => [(string | undefined)?, (number | undefined)?]

    z.partial(z.tuple([z.string()], z.number()));
    // => [(string | undefined)?, ...number[]]
    ```
  </Tab>
</Tabs>

## Unions

Union types (`A | B`) represent a logical "OR". Zod union schemas will check the input against each option in order. The first value that validates successfully is returned.

```ts
const stringOrNumber = z.union([z.string(), z.number()]);
// string | number

stringOrNumber.parse("foo"); // passes
stringOrNumber.parse(14); // passes
```

To extract the internal option schemas:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    stringOrNumber.options; // [ZodString, ZodNumber]
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    stringOrNumber.def.options; // [ZodString, ZodNumber]
    ```
  </Tab>
</Tabs>

{/* For convenience, you can also use the [`.or` method](#or):

  ```ts
  const stringOrNumber = z.string().or(z.number());
  ``` */}

{/* **Optional string validation:**

  To validate an optional form input, you can union the desired string validation with an empty string [literal](#literals).

  This example validates an input that is optional but needs to contain a [valid URL](#strings):

  ```ts
  const optionalUrl = z.union([z.string().url().nullish(), z.literal("")]);

  console.log(optionalUrl.safeParse(undefined).success); // true
  console.log(optionalUrl.safeParse(null).success); // true
  console.log(optionalUrl.safeParse("").success); // true
  console.log(optionalUrl.safeParse("https://zod.dev").success); // true
  console.log(optionalUrl.safeParse("not a valid url").success); // false
  ```

  <br/> */}

## Exclusive unions (XOR)

An exclusive union (XOR) is a union where exactly one option must match. Unlike regular unions that succeed when any option matches, `z.xor()` fails if zero options match OR if multiple options match.

```ts
const schema = z.xor([z.string(), z.number()]);

schema.parse("hello"); // ✅ passes
schema.parse(42);      // ✅ passes
schema.parse(true);    // ❌ fails (zero matches)
```

This is useful when you want to ensure mutual exclusivity between options:

```ts
// Validate that exactly ONE of these matches
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" }); // ✅ passes
```

If the input could match multiple options, `z.xor()` will fail. The resulting issue has `inclusive: false` and a `matches` array listing the indices of the options that matched:

```ts
const overlapping = z.xor([z.string(), z.any()]);
overlapping.parse("hello"); // ❌ fails (matches both string and any)
// ZodError: Invalid input: more than one option matched
```

Object options overlap more often than you'd expect, since `z.object()` strips unknown keys instead of rejecting them. Below, an input with both keys matches *both* options — the first strips `version`, the second keeps it. Use `z.strictObject()` (or `.strict()`) on the narrower option to make the branches mutually exclusive:

```ts
const name = z.object({ name: z.string() });
const version = name.extend({ version: z.string() });

z.xor([name, version]).parse({ name: "zod", version: "4" });           // ❌ fails (matches both)
z.xor([name.strict(), version]).parse({ name: "zod", version: "4" });  // ✅ passes
```

## Discriminated unions

A [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions) is a special kind of union in which a) all the options are object schemas that b) share a particular key (the "discriminator"). Based on the value of the discriminator key, TypeScript is able to "narrow" the type signature as you'd expect.

```ts
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
  }
}
```

You could represent it with a regular `z.union()`. But regular unions are *naive*—they check the input against each option in order and return the first one that passes. This can be slow for large unions.

So Zod provides a `z.discriminatedUnion()` API that uses a *discriminator key* to make parsing more efficient.

```ts
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 prop (`status` in the example above) corresponds to some literal value or set of values, usually `z.enum()`, `z.literal()`, `z.null()`, or `z.undefined()`.

<Accordions type="single">
  <Accordion title="Nesting discriminated unions">
    For advanced use cases, discriminated unions can be nested. Zod will figure out the optimal parsing strategy to leverage the discriminators at each level.

    ```ts
    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
    ]);
    ```
  </Accordion>
</Accordions>

### `z.getDiscriminatedOption()`

To retrieve a single option by its discriminator value. The value must be one the union declares; anything else is a TypeScript error.

```ts
const Success = z.object({ status: z.literal("success"), data: z.string() });
const Failed = z.object({ status: z.literal("failed"), error: z.string() });
const MyResult = z.discriminatedUnion("status", [Success, Failed]);

z.getDiscriminatedOption(MyResult, "success"); // typeof Success
z.getDiscriminatedOption(MyResult, "pending"); // ❌ not a declared discriminator
```

The option is returned as-is, so `.shape` and the other object methods keep working.

If multiple options accept an absent discriminator, looking up `undefined` throws an ambiguity error. Their unique explicit tags remain retrievable.

## Intersections

Intersection types (`A & B`) represent a logical "AND".

```ts
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>; // => number
```

This can be useful for intersecting two object types.

```ts
const Person = z.object({ name: z.string() });
type Person = z.infer<typeof Person>;

const Employee = z.object({ role: z.string() });
type Employee = z.infer<typeof Employee>;

const EmployedPerson = z.intersection(Person, Employee);
type EmployedPerson = z.infer<typeof EmployedPerson>;
// Person & Employee
```

<Callout type="warn">
  When merging object schemas, prefer [`A.extend(B)`](#extend) over intersections. Using `.extend()` will give you a new object schema, whereas `z.intersection(A, B)` returns a `ZodIntersection` instance which lacks common object methods like `pick` and `omit`.
</Callout>

## Records

Record schemas are used to validate types such as `Record<string, string>`.

### `z.record`

```ts
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 that is assignable to `string | number | symbol`.

```ts
const Keys = z.union([z.string(), z.number(), z.symbol()]);
const AnyObject = z.record(Keys, z.unknown());
// Record<string | number | symbol, unknown>
```

To create an object schemas containing keys defined by an enum:

```ts
const Keys = z.enum(["id", "name", "email"]);
const Person = z.record(Keys, z.string());
// { id: string; name: string; email: string }
```

Zod supports numeric keys inside records in a way that closely mirrors TypeScript itself. A `number` schema, when used as a record key, will validate that the key is a valid "numeric string". Additional numerical constraints (min, max, step, etc.) will also be validated.

```ts
const numberKeys = z.record(z.number(), z.string());
numberKeys.parse({ 
  1: "one", // ✅
  2: "two", // ✅
  "1.5": "one", // ✅
  "-3": "two", // ✅
  abc: "one" // ❌
});

// further validation is also supported
const intKeys = z.record(z.int().step(1).min(0).max(10), z.string());
intKeys.parse({ 
  0: "zero", // ✅
  1: "one", // ✅
  2: "two", // ✅
  12: "twelve", // ❌
  abc: "one" // ❌
});
```

A value schema that can stand in for an absent key — `.default()`, `.prefault()`, `.optional()` — makes that key optional on the input side, the same as it does inside `z.object()`:

```ts
const Person = z.record(z.enum(["id", "name"]), z.string().default(""));

type Input = z.input<typeof Person>; // { id?: string; name?: string }
type Output = z.output<typeof Person>; // { id: string; name: string }
```

### `z.partialRecord`

<Callout>
  If you pass a `z.enum` as the first argument to `z.record()`, Zod will exhaustively check that all enum values exist in the input as keys. This behavior agrees with TypeScript:

  ```ts
  type MyRecord = Record<"a" | "b", string>;
  const myRecord: MyRecord = { a: "foo", b: "bar" }; // ✅
  const myRecord: MyRecord = { a: "foo" }; // ❌ missing required key `b`
  ```

  For partial key sets, use `z.partialRecord()`.
</Callout>

If you want a *partial* record type, use `z.partialRecord()`. This skips the special exhaustiveness checks Zod normally runs with `z.enum()` and `z.literal()` key schemas.

```ts
const Keys = z.enum(["id", "name", "email"]).or(z.never()); 
const Person = z.partialRecord(Keys, z.string());
// { id?: string; name?: string; email?: string }
```

### `z.looseRecord`

By default, `z.record()` errors on keys that don't match the key schema. Use `z.looseRecord()` to pass through non-matching keys unchanged. This is particularly useful when combined with intersections to model multiple pattern properties:

```ts
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
});
```

## Maps

```ts
const StringNumberMap = z.map(z.string(), z.number());
type StringNumberMap = z.infer<typeof StringNumberMap>; // Map<string, number>

const myMap: StringNumberMap = new Map();
myMap.set("one", 1);
myMap.set("two", 2);

StringNumberMap.parse(myMap);
```

Map schemas can be further constrained with the following utility methods.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    z.map(z.string(), z.number()).nonempty(); // must contain at least 1 item
    z.map(z.string(), z.number()).min(5); // must contain 5 or more items
    z.map(z.string(), z.number()).max(5); // must contain 5 or fewer items
    z.map(z.string(), z.number()).size(5); // must contain 5 items exactly
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.map(z.string(), z.number()).check(z.minSize(1)); // alias for .nonempty()
    z.map(z.string(), z.number()).check(z.minSize(5)); // must contain 5 or more items
    z.map(z.string(), z.number()).check(z.maxSize(5)); // must contain 5 or fewer items
    z.map(z.string(), z.number()).check(z.size(5)); // must contain 5 items exactly
    ```
  </Tab>
</Tabs>

## Sets

```ts
const NumberSet = z.set(z.number());
type NumberSet = z.infer<typeof NumberSet>; // Set<number>

const mySet: NumberSet = new Set();
mySet.add(1);
mySet.add(2);
NumberSet.parse(mySet);
```

Set schemas can be further constrained with the following utility methods.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    z.set(z.string()).nonempty(); // must contain at least 1 item
    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 5 items exactly
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.set(z.string()).check(z.minSize(1)); // alias for .nonempty()
    z.set(z.string()).check(z.minSize(5)); // must contain 5 or more items
    z.set(z.string()).check(z.maxSize(5)); // must contain 5 or fewer items
    z.set(z.string()).check(z.size(5)); // must contain 5 items exactly
    ```
  </Tab>
</Tabs>

## Files

To validate `File` instances:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const fileSchema = z.file();

    fileSchema.min(10_000); // minimum .size (bytes)
    fileSchema.max(1_000_000); // maximum .size (bytes)
    fileSchema.mime("image/png"); // MIME type
    fileSchema.mime(["image/png", "image/jpeg"]); // multiple MIME types
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const fileSchema = z.file();

    fileSchema.check(z.minSize(10_000)); // minimum .size (bytes)
    fileSchema.check(z.maxSize(1_000_000)); // maximum .size (bytes)
    fileSchema.check(z.mime("image/png")); // MIME type
    fileSchema.check(z.mime(["image/png", "image/jpeg"])); // multiple MIME types
    ```
  </Tab>
</Tabs>

## Promises

<Callout type="warn">
  **Deprecated** — `z.promise()` is deprecated. There are vanishingly few valid uses cases for a `Promise` schema. If you suspect a value might be a `Promise`, simply `await` it before parsing it with Zod.
</Callout>

<Accordions type="single">
  <Accordion title="See z.promise() documentation">
    ```ts
    const numberPromise = z.promise(z.number());
    ```

    "Parsing" works a little differently with promise schemas. Validation happens in two parts:

    1. Zod synchronously checks that the input is an instance of Promise (i.e. an object with `.then` and `.catch` methods.).
    2. Zod uses `.then` to attach an additional validation step onto the existing Promise. You'll have to use `.catch` on the returned Promise to handle validation failures.

    ```ts
    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
    };
    ```
  </Accordion>
</Accordions>

## Instanceof

You can use `z.instanceof` to check that the input is an instance of a class. This is useful to validate inputs against classes that are exported from third-party libraries.

```ts
class Test {
  name: string;
}

const TestSchema = z.instanceof(Test);

TestSchema.parse(new Test()); // ✅
TestSchema.parse("whatever"); // ❌
```

This works with built-in classes too.

```ts
z.instanceof(RegExp);
z.instanceof(URL);
z.instanceof(Error);
```

Parsing returns the exact object that was passed in. Unlike `z.object()`, nothing is cloned, so the prototype, the unlisted properties, and the object identity all survive.

```ts
const url = new URL("https://example.com");

z.instanceof(URL).parse(url) === url; // true
```

### `z.property()` \[#property]

To validate a particular property of a class instance against a Zod schema:

```ts
const blobSchema = z.instanceof(URL).check(
  z.property("protocol", z.literal("https:", "Only HTTPS allowed"))
);

blobSchema.parse(new URL("https://example.com")); // ✅
blobSchema.parse(new URL("http://example.com")); // ❌
```

The `z.property()` API works with any data type (but it's most useful when used in conjunction with `z.instanceof()`).

```ts
const blobSchema = z.string().check(
  z.property("length", z.number().min(10))
);

blobSchema.parse("hello there!"); // ✅
blobSchema.parse("hello."); // ❌
```

### `z.properties()` \[#properties]

To check several properties at once:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const okResponse = z.instanceof(Response).check(
      ...z.properties({
        status: z.number().min(200).max(299),
        redirected: z.literal(false),
      })
    );

    okResponse.parse(new Response("ok")); // ✅
    okResponse.parse(new Response("", { status: 404 })); // ❌ status
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const okResponse = z.instanceof(Response).check(
      ...z.properties({
        status: z.number().check(z.minimum(200), z.maximum(299)),
        redirected: z.literal(false),
      })
    );

    okResponse.parse(new Response("ok")); // ✅
    okResponse.parse(new Response("", { status: 404 })); // ❌ status
    ```
  </Tab>
</Tabs>

Each property is asserted in place. The schemas in the shape are validated but their results are discarded, so a transform or a default never changes the parsed value.

In Zod, `z.instanceof()` also has a `.properties()` method. It spreads the same check and narrows the inferred type:

```ts
const okResponse = z.instanceof(Response).properties({
  status: z.number().min(200).max(299),
  redirected: z.literal(false),
});

type OkResponse = z.infer<typeof okResponse>;
// => Response & { status: number; redirected: false }
```

## Refinements

Every Zod schema stores an array of *refinements*. Refinements are a way to perform custom validation that Zod doesn't provide a native API for.

### `.refine()`

{/* <Callout>
  Checks do not (in fact, cannot) change the inferred type of the schema.
  </Callout>

  ### `.refine()` */}

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const myString = z.string().refine((val) => val.length <= 255);
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const myString = z.string().check(z.refine((val) => val.length <= 255));
    ```
  </Tab>
</Tabs>

<Callout type="warn">
  Refinement functions should never throw. Instead they should return a falsy value to signal failure. Thrown errors are not caught by Zod.
</Callout>

#### `error`

To customize the error message:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const myString = z.string().refine((val) => val.length > 8, { 
      error: "Too short!" 
    });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const myString = z.string().check(
      z.refine((val) => val.length > 8, { error: "Too short!" })
    );
    ```
  </Tab>
</Tabs>

The `error` option also accepts a function that receives the issue:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const myString = z.string().refine((val) => val.length > 8, {
      error: (iss) => `Too short: "${iss.input}"`
    });

    myString.parse("OH NO"); // ❌ Too short: "OH NO"
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const myString = z.string().check(
      z.refine((val) => val.length > 8, { error: (iss) => `Too short: "${iss.input}"` })
    );

    z.parse(myString, "OH NO"); // ❌ Too short: "OH NO"
    ```
  </Tab>
</Tabs>

#### `abort`

By default, validation issues from checks are considered *continuable*; that is, Zod will execute *all* checks in sequence, even if one of them causes a validation error. This is usually desirable, as it means Zod can surface as many errors as possible in one go.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    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" }
    ] */
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const myString = z.string().check(
      z.refine((val) => val.length > 8, { error: "Too short!" }),
      z.refine((val) => val === val.toLowerCase(), { error: "Must be lowercase" })
    );

    const result = z.safeParse(myString, "OH NO");
    result.error?.issues;
    /* [
      { "code": "custom", "message": "Too short!" },
      { "code": "custom", "message": "Must be lowercase" }
    ] */
    ```
  </Tab>
</Tabs>

To mark a particular refinement as *non-continuable*, use the `abort` parameter. Validation will terminate if the check fails.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    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!" }]
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const myString = z.string().check(
      z.refine((val) => val.length > 8, { error: "Too short!", abort: true }),
      z.refine((val) => val === val.toLowerCase(), { error: "Must be lowercase", abort: true })
    );

    const result = z.safeParse(myString, "OH NO");
    result.error?.issues;
    // [ { "code": "custom", "message": "Too short!" }]
    ```
  </Tab>
</Tabs>

#### `path`

To customize the error path, use the `path` parameter. This is typically only useful in the context of object schemas.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const passwordForm = z
      .object({
        password: z.string(),
        confirm: z.string(),
      })
      .refine((data) => data.password === data.confirm, {
        error: "Passwords don't match",
        path: ["confirm"], // path of error
      });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const passwordForm = z
      .object({
        password: z.string(),
        confirm: z.string(),
      })
      .check(z.refine((data) => data.password === data.confirm, {
        error: "Passwords don't match",
        path: ["confirm"], // path of error
      }));
    ```
  </Tab>
</Tabs>

This will set the `path` parameter in the associated issue:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const result = passwordForm.safeParse({ password: "asdf", confirm: "qwer" });
    result.error.issues;
    /* [{
      "code": "custom",
      "path": [ "confirm" ],
      "message": "Passwords don't match"
    }] */
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const result = z.safeParse(passwordForm, { password: "asdf", confirm: "qwer" });
    result.error.issues;
    /* [{
      "code": "custom",
      "path": [ "confirm" ],
      "message": "Passwords don't match"
    }] */
    ```
  </Tab>
</Tabs>

To define an asynchronous refinement, just pass an `async` function:

```ts
const userId = z.string().refine(async (id) => {
  // verify that ID exists in database
  return true;
});
```

<Callout>
  If you use async refinements, you must use the `.parseAsync` method to parse data! Otherwise Zod will throw an error.

  <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
    <Tab value="Zod">
      ```ts
      const result = await userId.parseAsync("abc123");
      ```
    </Tab>

    <Tab value="Zod Mini">
      ```ts
      const result = await z.parseAsync(userId, "abc123");
      ```
    </Tab>
  </Tabs>
</Callout>

#### `when`

> **Note** — This is a power user feature and can absolutely be abused in ways that will increase the probability of uncaught errors originating from inside your refinements.

By default, refinements don't run if any *non-continuable* issues have already been encountered. Zod is careful to ensure the type signature of the value is correct before passing it into any refinement functions.

```ts
const schema = z.string().refine((val) => {
  return val.length > 8
});

schema.parse(1234); // invalid_type: refinement won't be executed
```

In some cases, you want finer control over when refinements run. For instance consider this "password confirm" check:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    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"],
      });

    schema.parse({
      password: "asdf",
      confirmPassword: "asdf",
      anotherField: 1234 // ❌ this error will prevent the password check from running
    });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const schema = z
      .object({
        password: z.string().check(z.minLength(8)),
        confirmPassword: z.string(),
        anotherField: z.string(),
      })
      .check(z.refine((data) => data.password === data.confirmPassword, {
        message: "Passwords do not match",
        path: ["confirmPassword"],
      }));

    schema.parse({
      password: "asdf",
      confirmPassword: "asdf",
      anotherField: 1234 // ❌ this error will prevent the password check from running
    });
    ```
  </Tab>
</Tabs>

An error on `anotherField` will prevent the password confirmation check from executing, even though the check doesn't depend on `anotherField`. To control when a refinement will run, use the `when` parameter:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const baseSchema = z.object({
      password: z.string().min(8),
      confirmPassword: z.string(),
      anotherField: z.string(),
    });

    const schema = baseSchema
      .refine((data) => data.password === data.confirmPassword, {
        message: "Passwords do not match",
        path: ["confirmPassword"],

        // run if password & confirmPassword are valid
        when(payload) { // [!code ++]
          return baseSchema // [!code ++]
            .pick({ password: true, confirmPassword: true }) // [!code ++]
            .safeParse(payload.value).success; // [!code ++]
        },  // [!code ++]
      });

    schema.parse({
      password: "asdf",
      confirmPassword: "asdf",
      anotherField: 1234 // ❌ this error will not prevent the password check from running
    });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const schema = z
      .object({
        password: z.string().min(8),
        confirmPassword: z.string(),
        anotherField: z.string(),
      })
      .check(z.refine((data) => data.password === data.confirmPassword, {
        message: "Passwords do not match",
        path: ["confirmPassword"],

        when(payload) { // [!code ++]
          // no issues with `password` or `confirmPassword` // [!code ++]
          return payload.issues.every((iss) => { // [!code ++]
            const firstPathEl = iss.path?.[0]; // [!code ++]
            return firstPathEl !== "password" && firstPathEl !== "confirmPassword"; // [!code ++]
          }); // [!code ++]
        },  // [!code ++]
      }));

    schema.parse({
      password: "asdf",
      confirmPassword: "asdf",
      anotherField: 1234 // ❌ this error will prevent the password check from running
    });
    ```
  </Tab>
</Tabs>

### `.superRefine()`

The regular `.refine` API only generates a single issue with a `"custom"` error code, but `.superRefine()` makes it possible to create multiple issues using any of Zod's [internal issue types](https://github.com/colinhacks/zod/blob/main/packages/zod/src/v4/core/errors.ts).

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    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,
        });
      }
    });


    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const UniqueStringArray = z.array(z.string()).check(
      z.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,
          });
        }
      })
    );
    ```
  </Tab>
</Tabs>

### `.check()`

<Callout>
  **Note** — The `.check()` API is a more low-level API that's generally more complex than `.superRefine()`. It can be faster in performance-sensitive code paths, but it's also more verbose.
</Callout>

<Accordions>
  <Accordion title="View example">
    The `.refine()` API is syntactic sugar atop a more versatile (and verbose) API called `.check()`. You can use this API to create multiple issues in a single refinement or have full control of the generated issue objects.

    <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
      <Tab value="Zod">
        ```ts
        const UniqueStringArray = z.array(z.string()).check((ctx) => {
          if (ctx.value.length > 3) {
            // full control of issue objects
            ctx.issues.push({
              code: "too_big",
              maximum: 3,
              origin: "array",
              inclusive: true,
              message: "Too many items 😡",
              input: ctx.value
            });
          }

          // create multiple issues in one refinement
          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)
            });
          }
        });
        ```
      </Tab>

      <Tab value="Zod Mini">
        ```ts
        const UniqueStringArray = z.array(z.string()).check((ctx) => {
          // full control of issue objects
          if (ctx.value.length > 3) {
            ctx.issues.push({
              code: "too_big",
              maximum: 3,
              origin: "array",
              inclusive: true,
              message: "Too many items 😡",
              input: ctx.value
            });
          }

        // create multiple issues in one refinement
          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)
            });
          }
        });
        ```
      </Tab>
    </Tabs>
  </Accordion>
</Accordions>

## Codecs

> Introduced in `zod@4.1`. Refer to the dedicated [Codecs](/codecs) page for more information.

Codecs are a special kind of schema that implement *bidirectional transformations* between two other schemas.

```ts
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
  }
);
```

A regular `.parse()` operations performs the *forward transform*. It calls the codec's `decode` function.

```ts
stringToDate.parse("2024-01-15T10:30:00.000Z"); // => Date
```

You can alternatively use the top-level `z.decode()` function. Unlike `.parse()` (which accepts `unknown` input), `z.decode()` expects a strongly-typed input (`string` in this example).

```ts
z.decode(stringToDate, "2024-01-15T10:30:00.000Z"); // => Date
```

To perform the *reverse transform*, use the inverse: `z.encode()`.

```ts
z.encode(stringToDate, new Date("2024-01-15")); // => "2024-01-15T00:00:00.000Z"
```

Use `z.invertCodec()` to derive a new codec with the input and output schemas swapped.

```ts
const dateToString = z.invertCodec(stringToDate);

z.decode(dateToString, new Date("2024-01-15")); // => string
z.encode(dateToString, "2024-01-15T00:00:00.000Z"); // => Date
```

Refer to the dedicated [Codecs](/codecs) page for more information. That page contains implementations for commonly-needed codecs that you can copy/paste into your project:

* [**`stringToNumber`**](/codecs#stringtonumber)
* [**`stringToInt`**](/codecs#stringtoint)
* [**`stringToBigInt`**](/codecs#stringtobigint)
* [**`numberToBigInt`**](/codecs#numbertobigint)
* [**`isoDatetimeToDate`**](/codecs#isodatetimetodate)
* [**`epochSecondsToDate`**](/codecs#epochsecondstodate)
* [**`epochMillisToDate`**](/codecs#epochmillistodate)
* [**`jsonCodec`**](/codecs#jsoncodec)
* [**`utf8ToBytes`**](/codecs#utf8tobytes)
* [**`bytesToUtf8`**](/codecs#bytestoutf8)
* [**`base64ToBytes`**](/codecs#base64tobytes)
* [**`base64urlToBytes`**](/codecs#base64urltobytes)
* [**`hexToBytes`**](/codecs#hextobytes)
* [**`stringToURL`**](/codecs#stringtourl)
* [**`stringToHttpURL`**](/codecs#stringtohttpurl)
* [**`uriComponent`**](/codecs#uricomponent)
* [**`stringToBoolean`**](/codecs#stringtoboolean)

## Pipes

Schemas can be chained together into "pipes". Pipes are primarily useful when used in conjunction with [Transforms](#transforms).

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const stringToLength = z.string().pipe(z.transform(val => val.length));

    stringToLength.parse("hello"); // => 5
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const stringToLength = z.pipe(z.string(), z.transform(val => val.length));

    z.parse(stringToLength, "hello"); // => 5
    ```
  </Tab>
</Tabs>

### `z.input()` and `z.output()` \[#input-output]

Runtime counterparts to the type-level `z.input<T>` / `z.output<T>`: they replace every pipe in a schema with its input or output side. Useful for reaching [codecs](/codecs) nested inside objects, records, or maps, where `.in` / `.out` can't.

```ts
const Event = z.object({ name: z.string(), at: stringToDate });

z.input(Event).parse({ name: "launch", at: "2024-01-01T00:00:00Z" }); // ✅
z.output(Event).parse({ name: "launch", at: new Date() });            // ✅
```

Only codecs have two real sides. `z.output()` on a one-way [transform](#transforms) returns the transform, which validates nothing; `z.input()` on a `z.preprocess()` returns the schema the preprocessor feeds.

A wrapper's stored value survives only on the side it belongs to, so with a codec underneath, `z.input()` drops a `.default()` or `.catch()` and `z.output()` drops a `.prefault()`.

## Transforms

> **Note** — For bi-directional transforms, use [codecs](/codecs).

Transforms are a special kind of schema that perform a unidirectional transformation. Instead of validating input, they accept anything and perform some transformation on the data. To define a transform:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const castToString = z.transform((val) => String(val));

    castToString.parse("asdf"); // => "asdf"
    castToString.parse(123); // => "123"
    castToString.parse(true); // => "true"
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const castToString = z.transform((val) => String(val));

    z.parse(castToString, "asdf"); // => "asdf"
    z.parse(castToString, 123); // => "123"
    z.parse(castToString, true); // => "true"
    ```
  </Tab>
</Tabs>

<Callout type="warn">
  Transform functions should never throw. Thrown errors are not caught by Zod.
</Callout>

{/* The output type of the schema is inferred from the transform function:

  <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
  ```ts
  const castToString = z.transform((val) => String(val));

  type CastToString = z.infer<typeof castToString>; // string
  ```
  </Tab>
  <Tab value="Zod Mini">
  ```ts
  const castToString = z.transform((val) => String(val));

  type CastToString = z.infer<typeof castToString>; // string
  ```
  </Tab>
  </Tabs> */}

To perform validation logic inside a transform, use `ctx`. To report a validation issue, push a new issue onto `ctx.issues` (similar to the [`.check()`](#check) API).

```ts
const coercedInt = z.transform((val, ctx) => {
  try {
    const parsed = Number.parseInt(String(val));
    return parsed;
  } catch (e) {
    ctx.issues.push({
      code: "custom",
      message: "Not a number",
      input: val,
    });

    // this is a special constant with type `never`
    // returning it lets you exit the transform without impacting the inferred return type
    return z.NEVER;
  }
});
```

Most commonly, transforms are used in conjunction with [Pipes](#pipes). This combination is useful for performing some initial validation, then transforming the parsed data into another form.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const stringToLength = z.string().pipe(z.transform(val => val.length));

    stringToLength.parse("hello"); // => 5
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const stringToLength = z.pipe(z.string(), z.transform(val => val.length));

    z.parse(stringToLength, "hello"); // => 5
    ```
  </Tab>
</Tabs>

### `.transform()`

Piping some schema into a transform is a common pattern, so Zod provides a convenience `.transform()` method.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const stringToLength = z.string().transform(val => val.length); 
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    // no equivalent
    ```
  </Tab>
</Tabs>

Transforms can also be async:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const idToUser = z
      .string()
      .transform(async (id) => {
        // fetch user from database
        return db.getUserById(id); 
      });

    const user = await idToUser.parseAsync("abc123");
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const idToUser = z.pipe(
      z.string(),
      z.transform(async (id) => {
        // fetch user from database
        return db.getUserById(id); 
      }));

    const user = await idToUser.parse("abc123");
    ```
  </Tab>
</Tabs>

<Callout>
  If you use async transforms, you must use a `.parseAsync` or `.safeParseAsync` when parsing data! Otherwise Zod will throw an error.
</Callout>

### `.preprocess()`

Piping a transform into another schema is another common pattern, so Zod provides a convenience `z.preprocess()` function.

```ts
const coercedInt = z.preprocess((val) => {
  if (typeof val === "string") {
    return Number.parseInt(val);
  }
  return val;
}, z.int());
```

By default, the input type of a `z.preprocess()` schema is `unknown`, since the preprocessor is expected to handle arbitrary input. To narrow the input type, annotate the preprocessor's parameter directly:

```ts
const trimmed = z.preprocess(
  (val: string | null | undefined) => val?.trim() ?? "",
  z.string()
);

type Input = z.input<typeof trimmed>;  // string | null | undefined
type Output = z.output<typeof trimmed>; // string
```

This is useful when integrating with libraries like `react-hook-form` that derive their form value type from `z.input<>`.

## Defaults

To set a default value for a schema:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const defaultTuna = z.string().default("tuna");

    defaultTuna.parse(undefined); // => "tuna"
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const defaultTuna = z._default(z.string(), "tuna");

    defaultTuna.parse(undefined); // => "tuna"
    ```
  </Tab>
</Tabs>

Alternatively, you can pass a function which will be re-executed whenever a default value needs to be generated:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const randomDefault = z.number().default(Math.random);

    randomDefault.parse(undefined);    // => 0.4413456736055323
    randomDefault.parse(undefined);    // => 0.1871840107401901
    randomDefault.parse(undefined);    // => 0.7223408162401552
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const randomDefault = z._default(z.number(), Math.random);

    z.parse(randomDefault, undefined); // => 0.4413456736055323
    z.parse(randomDefault, undefined); // => 0.1871840107401901
    z.parse(randomDefault, undefined); // => 0.7223408162401552
    ```
  </Tab>
</Tabs>

## Prefaults

In Zod, setting a *default* value will short-circuit the parsing process. If the input is `undefined`, the default value is eagerly returned. As such, the default value must be assignable to the *output type* of the schema.

```ts
const schema = z.string().transform(val => val.length).default(0);
schema.parse(undefined); // => 0
```

Sometimes, it's useful to define a *prefault* ("pre-parse default") value. If the input is `undefined`, the prefault value will be parsed instead. The parsing process is *not* short circuited. As such, the prefault value must be assignable to the *input type* of the schema.

```ts
const schema = z.string().transform(val => val.length).prefault("tuna");
schema.parse(undefined); // => 4
```

This is also useful if you want to pass some input value through some mutating refinements.

```ts
const a = z.string().trim().toUpperCase().prefault("  tuna  ");
a.parse(undefined); // => "TUNA"

const b = z.string().trim().toUpperCase().default("  tuna  ");
b.parse(undefined); // => "  tuna  "
```

## Catch

Use `.catch()` to define a fallback value to be returned in the event of a validation error:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const numberWithCatch = z.number().catch(42);

    numberWithCatch.parse(5); // => 5
    numberWithCatch.parse("tuna"); // => 42
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const numberWithCatch = z.catch(z.number(), 42);

    numberWithCatch.parse(5); // => 5
    numberWithCatch.parse("tuna"); // => 42
    ```
  </Tab>
</Tabs>

Alternatively, you can pass a function which will be re-executed whenever a catch value needs to be generated.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const numberWithRandomCatch = z.number().catch((ctx) => {
      ctx.error; // the caught ZodError

      return Math.random();
    });

    numberWithRandomCatch.parse("sup"); // => 0.4413456736055323
    numberWithRandomCatch.parse("sup"); // => 0.1871840107401901
    numberWithRandomCatch.parse("sup"); // => 0.7223408162401552
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const numberWithRandomCatch = z.catch(z.number(), (ctx) => {
      ctx.value;   // the input value
      ctx.issues;  // the caught validation issue
      return Math.random();
    });

    z.parse(numberWithRandomCatch, "sup"); // => 0.4413456736055323
    z.parse(numberWithRandomCatch, "sup"); // => 0.1871840107401901
    z.parse(numberWithRandomCatch, "sup"); // => 0.7223408162401552
    ```
  </Tab>
</Tabs>

## Branded types

TypeScript's type system is [structural](https://www.typescriptlang.org/docs/handbook/type-compatibility.html), meaning that two types that are structurally equivalent are considered the same.

```ts
type Cat = { name: string };
type Dog = { name: string };

const pluto: Dog = { name: "pluto" };
const simba: Cat = pluto; // works fine
```

In some cases, it can be desirable to simulate [nominal typing](https://en.wikipedia.org/wiki/Nominal_type_system) inside TypeScript. This can be achieved with *branded types* (also known as "opaque types").

```ts
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; // ❌ not allowed
```

Under the hood, this works by attaching a "brand" to the schema's inferred type.

```ts
const Cat = z.object({ name: z.string() }).brand<"Cat">();
type Cat = z.output<typeof Cat>; // { name: string } & z.$brand<"Cat">
```

With this brand, plain (unbranded) data structures are not assignable to the inferred type. You have to parse some data with the schema to get branded data.

> Note that branded types do not affect the runtime result of `.parse`. It is a static-only construct.

By default, only the *output type* is branded.

```ts
const USD = z.string().brand<"USD">();

type USDOutput = z.output<typeof USD>; // string & z.$brand<"USD">
type USDInput = z.input<typeof USD>; // string
```

To customize this, pass a second generic to `.brand()` to specify the direction of the brand.

```ts
// requires Zod 4.2+
z.string().brand<"Cat", "out">(); // output is branded (default)
z.string().brand<"Cat", "in">(); // input is branded
z.string().brand<"Cat", "inout">(); // both are branded
```

## Readonly

To mark a schema as readonly:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const ReadonlyUser = z.object({ name: z.string() }).readonly();
    type ReadonlyUser = z.infer<typeof ReadonlyUser>;
    // Readonly<{ name: string }>
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const ReadonlyUser = z.readonly(z.object({ name: z.string() }));
    type ReadonlyUser = z.infer<typeof ReadonlyUser>;
    // Readonly<{ name: string }>
    ```
  </Tab>
</Tabs>

The inferred type is marked as `readonly`. Note that in TypeScript, this only affects objects, arrays, tuples, `Set`, and `Map`:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    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>
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.readonly(z.object({ name: z.string() })); // { readonly name: string }
    z.readonly(z.array(z.string())); // readonly string[]
    z.readonly(z.tuple([z.string(), z.number()])); // readonly [string, number]
    z.readonly(z.map(z.string(), z.date())); // ReadonlyMap<string, Date>
    z.readonly(z.set(z.string())); // ReadonlySet<string>
    ```
  </Tab>
</Tabs>

Inputs will be parsed like normal, then the result will be frozen with [`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) to prevent modifications.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const result = ReadonlyUser.parse({ name: "fido" });
    result.name = "simba"; // throws TypeError
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const result = z.parse(ReadonlyUser, { name: "fido" });
    result.name = "simba"; // throws TypeError
    ```
  </Tab>
</Tabs>

## JSON

To validate any JSON-encodable value:

```ts
const jsonSchema = z.json();
```

This is a convenience API that returns the following union schema:

```ts
const jsonSchema = z.lazy(() => {
  return z.union([
    z.string(params), 
    z.number(), 
    z.boolean(), 
    z.null(), 
    z.array(jsonSchema), 
    z.record(z.string(), jsonSchema)
  ]);
});
```

## Functions

Zod provides a `z.function()` utility for defining Zod-validated functions. This way, you can avoid intermixing validation code with your business logic.

```ts
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
```

Function schemas have an `.implement()` method which accepts a function and returns a new function that automatically validates its inputs and outputs.

```ts
const computeTrimmedLength = MyFunction.implement((input) => {
  // TypeScript knows input is a string!
  return input.trim().length;
});

computeTrimmedLength("sandwich"); // => 8
computeTrimmedLength(" asdf "); // => 4
```

This function will throw a `ZodError` if the input is invalid:

```ts
computeTrimmedLength(42); // throws ZodError
```

If you only care about validating inputs, you can omit the `output` field.

```ts
const MyFunction = z.function({
  input: [z.string()], // parameters (must be an array or a ZodTuple)
});

const computeTrimmedLength = MyFunction.implement((input) => input.trim.length);
```

Use the `.implementAsync()` method to create an async function.

```ts
const computeTrimmedLengthAsync = MyFunction.implementAsync(
  async (input) => input.trim().length
);

computeTrimmedLengthAsync("sandwich"); // => Promise<8>
```

## Custom

You can create a Zod schema for any TypeScript type by using `z.custom()`. This is useful for validating types from third-party libraries or any other type that isn't covered by a built-in schema. For class instances, prefer [`z.instanceof()`](#instanceof); for template literal types, prefer [`z.templateLiteral()`](#template-literals).

```ts
import { Decimal } from "decimal.js";

const decimalSchema = z.custom<Decimal>((val) => Decimal.isDecimal(val));

decimalSchema.parse(new Decimal("1.5")); // passes
decimalSchema.parse("1.5");              // throws
```

If you don't provide a validation function, Zod will allow any value. This can be dangerous!

```ts
z.custom<{ arg: string }>(); // performs no validation
```

You can customize the error message and other options by passing a second argument. This parameter works the same way as the params parameter of [`.refine`](#refine).

```ts
z.custom<...>((val) => ..., "custom error message");
```

## Apply

Use `.apply()` to incorporate external functions into Zod's method chain:

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    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
    schema.parse(101); // ❌ throws
    schema.parse(null); // => null
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    function setCommonNumberChecks<T extends z.ZodMiniNumber>(schema: T) {
      return schema.check(
        z.minimum(0), z.maximum(100)
      );
    }

    const schema = z.nullable(
      z.number().apply(setCommonNumberChecks)
    );

    z.parse(schema, 0);   // => 0
    z.parse(schema, -1);  // ❌ throws
    z.parse(schema, 101); // ❌ throws
    z.parse(schema, null); // => null
    ```
  </Tab>
</Tabs>

Additional arguments are passed through to the function:

```ts
function withDefault<T extends z.ZodType>(schema: T, value: z.output<T>) {
  return schema.nullish().transform((val) => val ?? value);
}

const schema = z.string().apply(withDefault, "anonymous");

schema.parse(undefined); // => "anonymous"
schema.parse("sandwich"); // => "sandwich"
```

## Matching an existing type

Sometimes the type comes first: a database model, a type from a generated client, an interface you don't own. Pass it to `z.toZod<T>()` and TypeScript checks that the schema's output type is exactly `T`.

```ts
type Player = {
  username: string;
  xp: number;
};

const Player = z.toZod<Player>()(
  z.object({
    username: z.string(),
    xp: z.number(),
  })
);

Player.shape.username; // ZodString — the schema is returned unchanged
```

The check is exact type equality, so any drift from the type is a compile error:

```ts
z.toZod<Player>()(
  z.object({
    username: z.string(),
    xp: z.number(),
    admin: z.boolean(), // ❌ extra key
  })
);
```

The usual alternative is `satisfies z.ZodType<Player>`, which only checks assignability. It catches a missing required key, but extra keys, omitted optional keys, and a bare `z.any()` all slip through:

```ts
z.object({
  username: z.string(),
  xp: z.number(),
  admin: z.boolean(),
}) satisfies z.ZodType<Player>; // ✅ no error

z.any() satisfies z.ZodType<Player>; // ✅ no error
```

An enum target matches `z.enum(...)`, and stays nominal — a plain literal union doesn't satisfy it:

```ts
enum Level {
  Noob = "noob",
  Pro = "pro",
}

z.toZod<{ level: Level }>()(z.object({ level: z.enum(Level) })); // ✅
z.toZod<{ level: Level }>()(z.object({ level: z.enum(["noob", "pro"]) })); // ❌
```


# Basic usage

import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
import { Callout } from "fumadocs-ui/components/callout";

{/* This page is a carefully curated introduction: the one workflow every new user walks through (define a schema, parse, infer, handle errors), shown once, in its simplest form. Edge cases, carve-outs, exceptions, and behavior notes for specific input shapes do NOT belong here, however true or recently fixed — they go on the relevant API reference page. If an addition explains when something *doesn't* work, it doesn't go on this page. */}

This page will walk you through the basics of creating schemas, parsing data, and using inferred types. For complete documentation on Zod's schema API, refer to [Defining schemas](/api).

## Defining a schema

Before you can do anything else, you need to define a schema. For the purposes of this guide, we'll use a simple object schema.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    import * as z from "zod"; 

    const Player = z.object({ 
      username: z.string(),
      xp: z.number()
    });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    import * as z from "zod/mini"

    const Player = z.object({ 
      username: z.string(),
      xp: z.number()
    });
    ```
  </Tab>
</Tabs>

## Parsing data

Given any Zod schema, use `.parse` to validate an input. If it's valid, Zod returns a strongly-typed *deep clone* of the input.

```ts
Player.parse({ username: "billie", xp: 100 }); 
// => returns { username: "billie", xp: 100 }
```

<Callout>
  **Note** — If your schema uses certain asynchronous APIs like `async` [refinements](/api#refinements) or [transforms](/api#transforms), you'll need to use the `.parseAsync()` method instead.

  ```ts
  await Player.parseAsync({ username: "billie", xp: 100 }); 
  ```
</Callout>

## Handling errors

When validation fails, the `.parse()` method will throw a `ZodError` instance with granular information about the validation issues.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    try {
      Player.parse({ username: 42, xp: "100" });
    } catch(error){
      if(error instanceof z.ZodError){
        error.issues; 
        /* [
          {
            expected: 'string',
            code: 'invalid_type',
            path: [ 'username' ],
            message: 'Invalid input: expected string, received number'
          },
          {
            expected: 'number',
            code: 'invalid_type',
            path: [ 'xp' ],
            message: 'Invalid input: expected number, received string'
          }
        ] */
      }
    }
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    try {
      Player.parse({ username: 42, xp: "100" });
    } catch(error){
      if(error instanceof z.core.$ZodError){
        error.issues; 
        /* [
          {
            expected: 'string',
            code: 'invalid_type',
            path: [ 'username' ],
            message: 'Invalid input'
          },
          {
            expected: 'number',
            code: 'invalid_type',
            path: [ 'xp' ],
            message: 'Invalid input'
          }
        ] */
      }
    }
    ```
  </Tab>
</Tabs>

To avoid a `try/catch` block, you can use the `.safeParse()` method to get back a plain result object containing either the successfully parsed data or a `ZodError`. The result type is a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions), so you can handle both cases conveniently.

```ts
const result = Player.safeParse({ username: 42, xp: "100" });
if (!result.success) {
  result.error;   // ZodError instance
} else {
  result.data;    // { username: string; xp: number }
}
```

<Callout>
  **Note** — If your schema uses certain asynchronous APIs like `async` [refinements](/api#refinements) or [transforms](/api#transforms), you'll need to use the `.safeParseAsync()` method instead.

  ```ts
  await schema.safeParseAsync("hello");
  ```
</Callout>

When you only need to know whether an input is acceptable, use `.validate()` — or the top-level `z.validate(schema, data)`, which is the form `zod/mini` uses. It returns a boolean, never builds an error, and acts as a type guard on the schema's input type. On invalid input it is up to 16x faster than `.safeParse().success`.

```ts
Player.validate({ username: "billie", xp: 100 }); // true
Player.validate({ username: 42, xp: "100" });     // false
```

Use `.validateAsync()` for schemas with async refinements or transforms. On a [compiled schema](/compile), `.validate()` answers directly from the compiled fast path.

## 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.

```ts
const Player = z.object({ 
  username: z.string(),
  xp: z.number()
});

// extract the inferred type
type Player = z.infer<typeof Player>;

// use it in your code
const player: Player = { username: "billie", xp: 100 };
```

In some cases, the input & 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:

```ts
const mySchema = z.string().transform((val) => val.length);

type MySchemaIn = z.input<typeof mySchema>;
// => string

type MySchemaOut = z.output<typeof mySchema>; // equivalent to z.infer<typeof mySchema>
// number
```

***

Now that we have the basics covered, let's jump into the Schema API.


# Codecs

import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
import { ThemedImage } from "@/components/themed-image";

> Introduced in `zod@4.1`.

All Zod schemas can process inputs in both the forward and backward direction:

* **Forward**: `Input` to `Output`
  * `.parse()`
  * `.decode()`
* **Backward**: `Output` to `Input`
  * `.encode()`

In most cases, this is a distinction without a difference. The input and output types are identical, so there's no difference between "forward" and "backward".

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const schema = z.string();

    type Input = z.input<typeof schema>;    // string
    type Output = z.output<typeof schema>;  // string

    schema.parse("asdf");   // => "asdf"
    schema.decode("asdf");  // => "asdf"
    schema.encode("asdf");  // => "asdf"
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const schema = z.string();

    type Input = z.input<typeof schema>;    // string
    type Output = z.output<typeof schema>;  // string

    z.parse(schema, "asdf");   // => "asdf"
    z.decode(schema, "asdf");  // => "asdf"
    z.encode(schema, "asdf");  // => "asdf"
    ```
  </Tab>
</Tabs>

However, some schema types cause the input and output types to diverge, notably `z.codec()`. Codecs are a special type of schema that defines a *bi-directional transformation* between two other schemas.

```ts
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
  }
);
```

In these cases, `z.decode()` and `z.encode()` behave quite differently.

<Tabs items={['Zod', 'Zod Mini']}>
  <Tab value="Zod">
    ```ts
    stringToDate.decode("2024-01-15T10:30:00.000Z")
    // => Date

    stringToDate.encode(new Date("2024-01-15T10:30:00.000Z"))
    // => string
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.decode(stringToDate, "2024-01-15T10:30:00.000Z")
    // => Date

    z.encode(stringToDate, new Date("2024-01-15T10:30:00.000Z"))
    // => string
    ```
  </Tab>
</Tabs>

> **Note** —There's nothing special about the directions or terminology here. Instead of *encoding* with an `A -> B` codec, you could instead *decode* with a `B -> A` codec. The use of the terms "decode" and "encode" is just a convention.

This is particularly useful when parsing data at a network boundary. You can share a single Zod schema between your client and server, then use this single schema to convert between a network-friendly format (say, JSON) and a richer JavaScript representation.

<ThemedImage lightSrc="/codecs/codecs-network-light.svg" darkSrc="/codecs/codecs-network-dark.svg" alt="Codecs encoding and decoding data across a network boundary" />

### Inverting codecs

Use `z.invertCodec()` to derive the reverse codec from an existing one. The returned codec swaps the input and output schemas, then swaps the `decode` and `encode` transforms.

<Tabs items={['Zod', 'Zod Mini']}>
  <Tab value="Zod">
    ```ts
    const dateToString = z.invertCodec(stringToDate);

    dateToString.decode(new Date("2024-01-15T10:30:00.000Z"));
    // => string

    dateToString.encode("2024-01-15T10:30:00.000Z");
    // => Date
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const dateToString = z.invertCodec(stringToDate);

    z.decode(dateToString, new Date("2024-01-15T10:30:00.000Z"));
    // => string

    z.encode(dateToString, "2024-01-15T10:30:00.000Z");
    // => Date
    ```
  </Tab>
</Tabs>

`z.invertCodec()` only inverts the codec you pass to it. It does not recursively invert codecs nested inside another schema; invert those codecs where you define the reversed schema.

### Composability

> **Note** — You can use `z.encode()` and `z.decode()` with any schema. It doesn't have to be a ZodCodec.

Codecs are a schema like any other. You can nest them inside objects, arrays, pipes, etc. There are no rules on where you can use them!

```ts
const payloadSchema = z.object({ 
  startDate: stringToDate 
});

payloadSchema.decode({
  startDate: "2024-01-15T10:30:00.000Z"
}); // => { startDate: Date }
```

### Type-safe inputs

While `.parse()` and `.decode()` behave identically at *runtime*, they have different type signatures. The `.parse()` method accepts `unknown` as input, and returns a value that matches the schema's inferred *output type*. By contrast, the `z.decode()` and `z.encode()` functions have *strongly-typed inputs*.

```ts
stringToDate.parse(12345); 
// no complaints from TypeScript (fails at runtime)

stringToDate.decode(12345); 
// ❌ TypeScript error: Argument of type 'number' is not assignable to parameter of type 'string'.

stringToDate.encode(12345); 
// ❌ TypeScript error: Argument of type 'number' is not assignable to parameter of type 'Date'.
```

Why the difference? Encoding and decoding imply *transformation*. In many cases, the inputs to these methods are already strongly typed in application code, so z.decode/z.encode accept strongly typed inputs to surface mistakes at compile time.
Here's a diagram demonstrating the differences between the type signatures for `parse()`, `decode()`, and `encode()`.

<ThemedImage lightSrc="/codecs/codecs-light.png" darkSrc="/codecs/codecs-dark.png" alt="Codec directionality diagram showing bidirectional transformation between input and output schemas" />

### Async and safe variants

As with `.transform()` and `.refine()`, codecs support async transforms.

```ts
const asyncCodec = z.codec(z.string(), z.number(), {
  decode: async (str) => Number(str),
  encode: async (num) => num.toString(),
});
```

As with regular `parse()`, there are "safe" and "async" variants of `decode()` and `encode()`.

```ts
stringToDate.decode("2024-01-15T10:30:00.000Z"); 
// => Date

stringToDate.decodeAsync("2024-01-15T10:30:00.000Z"); 
// => Promise<Date>

stringToDate.safeDecode("2024-01-15T10:30:00.000Z"); 
// => { success: true, data: Date } | { success: false, error: ZodError }

stringToDate.safeDecodeAsync("2024-01-15T10:30:00.000Z"); 
// => Promise<{ success: true, data: Date } | { success: false, error: ZodError }>
```

## How encoding works

There are some subtleties to how certain Zod schemas "reverse" their parse behavior.

### Codecs

This one is fairly self-explanatory. Codecs encapsulate a bi-directional transformation between two types. `z.decode()` triggers the `decode` transform to convert input into a parsed value, while `z.encode()` triggers the `encode` transform to serialize it back.

```ts
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
  }
);

stringToDate.decode("2024-01-15T10:30:00.000Z"); 
// => Date

stringToDate.encode(new Date("2024-01-15")); 
// => string
```

### Pipes

> **Fun fact** — Codecs are implemented internally as *subclasses* of pipes with "interstitial" transform logic.

During regular decoding, a `ZodPipe<A, B>` schema will first parse the data with `A`, then pass it into `B`. As you might expect, during encoding, the data is first encoded with `B`, then passed into `A`.

### Refinements

All checks (`.refine()`, `.min()`, `.max()`, etc.) are still executed in both directions.

```ts
const schema = stringToDate.refine((date) => date.getFullYear() >= 2000, "Must be this millennium");

schema.encode(new Date("2000-01-01"));
// => Date

schema.encode(new Date("1999-01-01"));
// => ❌ ZodError: [
//   {
//     "code": "custom",
//     "path": [],
//     "message": "Must be this millennium"
//   }
// ]
```

To avoid unexpected errors in your custom `.refine()` logic, Zod performs two "passes" during `z.encode()`. The first pass ensures the input type conforms to the expected type (no `invalid_type` errors). If that passes, Zod performs the second pass which executes the refinement logic.

This approach also supports "mutating transforms" like `z.string().trim()` or `z.string().toLowerCase()`:

```ts
const schema = z.string().trim();

schema.decode("  hello  ");
// => "hello"

schema.encode("  hello  ");
// => "hello"
```

### Defaults and prefaults

Defaults and prefaults are only applied in the "forward" direction.

```ts
const stringWithDefault = z.string().default("hello");

stringWithDefault.decode(undefined); 
// => "hello"

stringWithDefault.encode(undefined); 
// => ZodError: Expected string, received undefined
```

When you attach a default value to a schema, the input becomes optional (`| undefined`) but the output does not. As such, `undefined` is not a valid input to `z.encode()` and defaults/prefaults will not be applied.

### Catch

Similarly, `.catch()` is only applied in the "forward" direction.

```ts
const stringWithCatch = z.string().catch("hello");

stringWithCatch.decode(1234); 
// => "hello"

stringWithCatch.encode(1234); 
// => ZodError: Expected string, received number
```

### Stringbool

> **Note** — [Stringbool](/api#stringbool) pre-dates the introduction of codecs in Zod. It has since been internally re-implemented as a codec.

The `z.stringbool()` API converts string values (`"true"`, `"false"`, `"yes"`, `"no"`, etc.) into `boolean`. By default, it will convert `true` to `"true"` and `false` to `"false"` during `z.encode()`.

```ts
const stringbool = z.stringbool();

stringbool.decode("true");  // => true
stringbool.decode("false"); // => false

stringbool.encode(true);    // => "true"
stringbool.encode(false);   // => "false"
```

If you specify a custom set of `truthy` and `falsy` values, the *first element in the array* will be used instead.

```ts
const stringbool = z.stringbool({ truthy: ["yes", "y"], falsy: ["no", "n"] });

stringbool.encode(true);    // => "yes"
stringbool.encode(false);   // => "no"
```

### Transforms

⚠️ — The `.transform()` API implements a *unidirectional* transformation. If any `.transform()` exists anywhere in your schema, attempting a `z.encode()` operation will throw a *runtime error* (not a `ZodError`).

```ts
const schema = z.string().transform(val => val.length);

schema.encode(1234); 
// ❌ Error: Encountered unidirectional transform during encode: ZodTransform
```

{/* ### Success

  `ZodSuccess` is also strictly unidirectional, and will throw an error if encountered during an encode operation.

  ```ts
  const successSchema = z.success(z.string());

  z.decode(successSchema, "hello"); 
  // => true

  z.encode(successSchema, true);    
  // ❌ Error: Encountered unidirectional transform during encode: ZodSuccess
  ``` */}

## Useful codecs

Below are implementations for a bunch of commonly-needed codecs. For the sake of customizability, these are not included as first-class APIs in Zod itself. Instead, you should copy/paste them into your project and modify them as needed.

> **Note** — The codec implementations below are tested for correctness.

### `stringToNumber`

Converts string representations of numbers to JavaScript `number` type using `parseFloat()`.

```ts
const stringToNumber = z.codec(z.string().regex(z.regexes.number), z.number(), {
  decode: (str) => Number.parseFloat(str),
  encode: (num) => num.toString(),
});

stringToNumber.decode("42.5");  // => 42.5
stringToNumber.encode(42.5);    // => "42.5"
```

### `stringToInt`

Converts string representations of integers to JavaScript `number` type using `parseInt()`.

```ts
const stringToInt = z.codec(z.string().regex(z.regexes.integer), z.int(), {
  decode: (str) => Number.parseInt(str, 10),
  encode: (num) => num.toString(),
});

stringToInt.decode("42");  // => 42
stringToInt.encode(42);    // => "42"
```

### `stringToBigInt`

Converts string representations to JavaScript `bigint` type.

```ts
const stringToBigInt = z.codec(z.string(), z.bigint(), {
  decode: (str) => BigInt(str),
  encode: (bigint) => bigint.toString(),
});

stringToBigInt.decode("12345");  // => 12345n
stringToBigInt.encode(12345n);   // => "12345"
```

### `numberToBigInt`

Converts JavaScript `number` to `bigint` type.

```ts
const numberToBigInt = z.codec(z.int(), z.bigint(), {
  decode: (num) => BigInt(num),
  encode: (bigint) => Number(bigint),
});

numberToBigInt.decode(42);   // => 42n
numberToBigInt.encode(42n);  // => 42
```

### `isoDatetimeToDate`

Converts ISO datetime strings to JavaScript `Date` objects.

```ts
const isoDatetimeToDate = z.codec(z.iso.datetime(), z.date(), {
  decode: (isoString) => new Date(isoString),
  encode: (date) => date.toISOString(),
});

isoDatetimeToDate.decode("2024-01-15T10:30:00.000Z");  // => Date object
isoDatetimeToDate.encode(new Date("2024-01-15"));       // => "2024-01-15T00:00:00.000Z"
```

### `epochSecondsToDate`

Converts Unix timestamps (seconds since epoch) to JavaScript `Date` objects.

```ts
const epochSecondsToDate = z.codec(z.int().min(0), z.date(), {
  decode: (seconds) => new Date(seconds * 1000),
  encode: (date) => Math.floor(date.getTime() / 1000),
});

epochSecondsToDate.decode(1705314600);  // => Date object
epochSecondsToDate.encode(new Date());  // => Unix timestamp in seconds
```

### `epochMillisToDate`

Converts Unix timestamps (milliseconds since epoch) to JavaScript `Date` objects.

```ts
const epochMillisToDate = z.codec(z.int().min(0), z.date(), {
  decode: (millis) => new Date(millis),
  encode: (date) => date.getTime(),
});

epochMillisToDate.decode(1705314600000);  // => Date object
epochMillisToDate.encode(new Date());     // => Unix timestamp in milliseconds
```

### `json(schema)`

Parses JSON strings into structured data and serializes back to JSON. This generic function accepts an output schema to validate the parsed JSON data.

```ts
const jsonCodec = <T extends z.core.$ZodType>(schema: T) =>
  z.codec(z.string(), schema, {
    decode: (jsonString, ctx) => {
      try {
        return JSON.parse(jsonString);
      } catch (err: any) {
        ctx.issues.push({
          code: "invalid_format",
          format: "json",
          input: jsonString,
          message: err.message,
        });
        return z.NEVER;
      }
    },
    encode: (value) => JSON.stringify(value),
  });
```

Usage example with a specific schema:

```ts
const jsonToObject = jsonCodec(z.object({ name: z.string(), age: z.number() }));

jsonToObject.decode('{"name":"Alice","age":30}');  
// => { name: "Alice", age: 30 }

jsonToObject.encode({ name: "Bob", age: 25 });     
// => '{"name":"Bob","age":25}'

jsonToObject.decode('~~invalid~~'); 
// ZodError: [
//   {
//     "code": "invalid_format",
//     "format": "json",
//     "path": [],
//     "message": "Unexpected token '~', \"~~invalid~~\" is not valid JSON"
//   }
// ]
```

### `utf8ToBytes`

Converts UTF-8 strings to `Uint8Array` byte arrays.

```ts
const utf8ToBytes = z.codec(z.string(), z.instanceof(Uint8Array), {
  decode: (str) => new TextEncoder().encode(str),
  encode: (bytes) => new TextDecoder().decode(bytes),
});

utf8ToBytes.decode("Hello, 世界!");  // => Uint8Array
utf8ToBytes.encode(bytes);          // => "Hello, 世界!"
```

### `bytesToUtf8`

Converts `Uint8Array` byte arrays to UTF-8 strings.

```ts
const bytesToUtf8 = z.codec(z.instanceof(Uint8Array), z.string(), {
  decode: (bytes) => new TextDecoder().decode(bytes),
  encode: (str) => new TextEncoder().encode(str),
});

bytesToUtf8.decode(bytes);          // => "Hello, 世界!"
bytesToUtf8.encode("Hello, 世界!");  // => Uint8Array
```

### `base64ToBytes`

Converts base64 strings to `Uint8Array` byte arrays and vice versa.

```ts
const base64ToBytes = z.codec(z.base64(), z.instanceof(Uint8Array), {
  decode: (base64String) => z.util.base64ToUint8Array(base64String),
  encode: (bytes) => z.util.uint8ArrayToBase64(bytes),
});

base64ToBytes.decode("SGVsbG8=");  // => Uint8Array([72, 101, 108, 108, 111])
base64ToBytes.encode(bytes);       // => "SGVsbG8="
```

### `base64urlToBytes`

Converts base64url strings (URL-safe base64) to `Uint8Array` byte arrays.

```ts
const base64urlToBytes = z.codec(z.base64url(), z.instanceof(Uint8Array), {
  decode: (base64urlString) => z.util.base64urlToUint8Array(base64urlString),
  encode: (bytes) => z.util.uint8ArrayToBase64url(bytes),
});

base64urlToBytes.decode("SGVsbG8");  // => Uint8Array([72, 101, 108, 108, 111])
base64urlToBytes.encode(bytes);      // => "SGVsbG8"
```

### `hexToBytes`

Converts hexadecimal strings to `Uint8Array` byte arrays and vice versa.

```ts
const hexToBytes = z.codec(z.hex(), z.instanceof(Uint8Array), {
  decode: (hexString) => z.util.hexToUint8Array(hexString),
  encode: (bytes) => z.util.uint8ArrayToHex(bytes),
});

hexToBytes.decode("48656c6c6f");     // => Uint8Array([72, 101, 108, 108, 111])
hexToBytes.encode(bytes);            // => "48656c6c6f"
```

### `stringToURL`

Converts URL strings to JavaScript `URL` objects.

```ts
const stringToURL = z.codec(z.url(), z.instanceof(URL), {
  decode: (urlString) => new URL(urlString),
  encode: (url) => url.href,
});

stringToURL.decode("https://example.com/path");  // => URL object
stringToURL.encode(new URL("https://example.com"));  // => "https://example.com/"
```

### `stringToHttpURL`

Converts HTTP/HTTPS URL strings to JavaScript `URL` objects.

```ts
const stringToHttpURL = z.codec(z.httpUrl(), z.instanceof(URL), {
  decode: (urlString) => new URL(urlString),
  encode: (url) => url.href,
});

stringToHttpURL.decode("https://api.example.com/v1");  // => URL object
stringToHttpURL.encode(url);                           // => "https://api.example.com/v1"
```

### `uriComponent`

Encodes and decodes URI components using `encodeURIComponent()` and `decodeURIComponent()`.

```ts
const uriComponent = z.codec(z.string(), z.string(), {
  decode: (encodedString) => decodeURIComponent(encodedString),
  encode: (decodedString) => encodeURIComponent(decodedString),
});

uriComponent.decode("Hello%20World%21");  // => "Hello World!"
uriComponent.encode("Hello World!");      // => "Hello%20World!"
```


# AOT compilation

import { Callout } from "fumadocs-ui/components/callout";
import { ThemedImage } from "@/components/themed-image";

Zod can compile a schema ahead of time into a flat, loop-free validator that runs several times faster than the standard parser. Results and errors are identical.

```ts
import * as z from "zod";

const Player = z.object({
  username: z.string(),
  bio: z.string(),
  xp: z.number(),
  // ...20 more properties...
});

const CompiledPlayer = z.compile(Player);
```

Use it exactly like `Player`:

```ts
Player.parse({ ... });
CompiledPlayer.parse({ ... }); // ~9x faster
```

A compiled schema like `CompiledPlayer` is a Zod schema like any other. *There are no special rules around compiled schemas.*

* Same methods: `.parse()`, `.safeParse()`, `.extend()`, `.optional()`, etc.
* Same inferred input and output types
* Same issues and error messages

Zod's entire test suite runs twice—once normally and again with auto-compilation enabled globally—to ensure perfect fidelity.

Containers like objects and tuples benefit the most, since compilation unrolls the runtime's per-key walk into flat loop-free validation logic that can be optimized by the JS engine.

<ThemedImage lightSrc="/blog/compile-speedup-light.svg" darkSrc="/blog/compile-speedup-dark.svg" alt="Time per parse on a shared nanosecond axis, standard parser as a gray bar with the compiled time as a blue bar inside it: an array of 10 objects 377 ns to 68 ns (5.5x), a 20-key object 301 ns to 38 ns (7.8x), an array of 10 strings 241 ns to 33 ns (7.3x), a union of 3 objects 190 ns to 36 ns (5.3x), a 3-element tuple 119 ns to 33 ns (3.6x), a 5-key strict object 117 ns to 32 ns (3.7x), a discriminated union 92 ns to 27 ns (3.4x), a 5-key object 76 ns to 28 ns (2.8x); up to 7.8x faster when compiled" caption={<>Time per parse, standard parser vs compiled — lower is better (<a href="https://github.com/colinhacks/zod/blob/main/packages/bench/compile-matrix.ts">benchmark</a>)</>} />

There are two ways to opt in.

## `z.compile()`

Compiles a single schema and returns a compiled copy. The original schema is unchanged.

Methods that derive a *new* schema (`.refine()`, `.extend()`, `.optional()`, `.meta()`, …) return uncompiled schemas. Compile the final schema, not an intermediate:

```ts
// ❌ the .refine() result is not compiled
const schema = z.compile(z.string()).refine((val) => val.length > 1);

// ✅ compile last
const schema2 = z.compile(z.string().refine((val) => val.length > 1));
```

## `import "zod/compile"`

Enables compilation globally. Every schema constructed *after* this import is compiled automatically the first time it parses.

```ts
import "zod/compile"; // must come before modules that define schemas
import * as z from "zod";

const schema = z.object({ name: z.string() });
schema.parse({ name: "ok" }); // compiled on first parse
```

Compilation is lazy, so only the schemas you actually parse with get compiled.

It also works as a Node.js CLI flag, which guarantees it runs before any module defines a schema:

```sh
node --import zod/compile app.js   # ESM
node --require zod/compile app.cjs # CommonJS
```

Or set `preload` in [`bunfig.toml`](https://bun.com/docs/runtime/bunfig#preload) or [`nub.jsonc`](https://nubjs.com/docs/config#preload).

```jsonc title="nub.jsonc"
{
  "preload": ["zod/compile"]
}
```

This import is for applications, not libraries.

## How it works

Under the hood, `z.compile()` walks the entire schema once and produces a hyperoptimized snippet of flat, loop-free JavaScript that can validate inputs far faster than a standard runtime validator. This snippet can be executed via `new Function()` (effectively a more powerful `eval`) to serve as a fast-path validator. Schemas use this to "fast check" validity, falling back to the regular runtime logic on validation failure to provide granular error information.

Take this simple `Point` schema:

```ts
const Point = z.object({
  x: z.number(),
  y: z.number()
});
```

Here is the generated snippet for it:

```ts
const isPoint = new Function("input", `
  if (typeof input !== "object" || input === null) return false;
  if (typeof input.x !== "number") return false;
  if (typeof input.y !== "number") return false;
  return true;
`);

isPoint({ x: 1, y: 2 }); // true
isPoint({ x: "1" });     // false
```

For the large majority of inputs, the generated function validates the data with the fastest logic JavaScript can express: straight-line `typeof` checks and property reads, with no interpreter in between. When it can't handle an input, Zod falls back to the standard parser.

This is the function Zod generates for the `Player` schema above:

```js
if (typeof input !== "object" || input === null || Array.isArray(input)) return INVALID;
const v0 = input["username"];
if (typeof v0 !== "string") return INVALID;
const v1 = input["bio"];
if (typeof v1 !== "string") return INVALID;
const v2 = input["xp"];
if (typeof v2 !== "number" || !Number.isFinite(v2)) return INVALID;
const v3 = { "username": v0, "bio": v1, "xp": v2 };
return v3;
```

Armed with the power of `new Function()`, this happens in-process at runtime. There is no need to integrate with your build system.

<Callout>The compiled schema is purely additive on top of the existing schema. It tacks on the pre-compiled fast path for checking valid inputs. When invalid data is detected, it returns the `INVALID` symbol to signal that parsing should fall back to the uncompiled parser. This structurally prevents subtle deviations in error reporting between compiled and uncompiled variants.</Callout>

On invalid input the fallback runs the uncompiled schema, so the error is the uncompiled schema's error. Two consequences:

* Invalid inputs pay for both the fast path and the fallback, so compilation barely speeds up failures. Nearly all of that cost is the fallback, which is what produces the errors.
* Refinements and transforms run once on valid input, and at most twice on invalid input.

## Unsupported schemas

Some features can't be compiled or don't benefit from compilation. In these cases, `z.compile()` ejects from compilation and returns the original schema unchanged:

```ts
const Schema = z.string().refine(async (val) => isAvailable(val));

z.compile(Schema); // returns Schema itself, uncompiled
```

* `async` refinements, transforms, and checks
* `z.xor()`
* recursive schemas
* `z.coerce.*`
* checks with a custom `when`
* `.catch()` given a callback (`.catch(value)` with a constant compiles normally)

Inside an object, array, tuple, record, or intersection, an unsupported child runs on the standard parser while the surrounding structure stays compiled. A union with an unsupported member, a `.catch()` callback, or anything async anywhere in the subtree makes the whole schema fall back.

Encoding (`z.encode()`, the codec `"backward"` direction) and async parsing always use the standard parser.

Pass `strict` to throw instead of falling back — for example, to confirm that a schema on a hot path really did compile:

```ts
z.compile(Schema, { strict: true }); // throws ZodCompileAsyncError
```

`ZodCompileAsyncError` is thrown for async schemas and `ZodCompileUnsupportedError` for everything else. Both are thrown only under `strict`.

## Content Security Policy

Compilation uses `new Function`, which is unavailable in CSP/no-`eval` environments. Global mode stands down when `jitless` is set:

```ts
z.config({ jitless: true });
```

Calling `z.compile()` directly is an explicit opt-in, so it attempts code generation regardless of `jitless`. Where the environment rejects `new Function`, the schema comes back uncompiled like any other refusal.

## Bundle size

The compiler is a lot of code, and invoking it via `z.compile()` or `"zod/compile"` means it will be included in your bundle. It adds about 7 KB gzipped (28 KB minified). A bundle that never calls `z.compile()` or imports `zod/compile` pays nothing; it will be tree-shaken completely during bundling.

| bundle (four-key object schema) | without compiler | with compiler |
| ------------------------------- | ---------------- | ------------- |
| Zod                             | 24.1 KB          | 31.1 KB       |
| Zod Mini                        | 4.6 KB           | 13.2 KB       |

## Benchmarks

The benefits scale with schema complexity. Each schema here is measured alone in a tight loop — the standard parser's best case — so the ratios run lower than in the chart at the top of the page ([benchmark](https://github.com/colinhacks/zod/blob/main/packages/bench/compile-scaling.ts)).

| schema          | speedup |
| --------------- | ------- |
| object, 5 keys  | 1.8x    |
| object, 10 keys | 2.2x    |
| object, 20 keys | 5.0x    |
| object, 50 keys | 10.2x   |
| tuple, 1 item   | 2.2x    |
| tuple, 3 items  | 2.5x    |
| tuple, 5 items  | 3.0x    |
| tuple, 10 items | 3.7x    |

Results on the [Moltar benchmark](https://github.com/moltar/typescript-runtime-type-benchmarks) fixture, comparing Zod compiled and uncompiled with other libraries. The `parseSafe` category returns a new object with unknown keys stripped.

<ThemedImage lightSrc="/blog/moltar-light.svg" darkSrc="/blog/moltar-dark.svg" alt="Bar chart of operations per second on the moltar benchmark fixture, parseSafe category: Zod 4 compiled 47.5M, typia 45.3M, Zod 4 11.6M, valibot 1.8M, effect 1.7M, Zod 3 1.2M, arktype 152k, yup 121k" caption={<>Throughput on the moltar benchmark fixture (parseSafe: returns a new object with unknown keys stripped) — higher is better (<a href="https://github.com/moltar/typescript-runtime-type-benchmarks/pull/2329">benchmark</a>)</>} />

The `assertLoose` category returns a boolean and allows unknown keys. Zod runs it through [`z.validate()`](/basics#handling-errors).

<ThemedImage lightSrc="/blog/moltar-assertLoose-light.svg" darkSrc="/blog/moltar-assertLoose-dark.svg" alt="Bar chart of operations per second on the moltar benchmark fixture, assertLoose category: typia 74.9M, arktype 66.2M, Zod 4 compiled 60.6M, Zod 4 6.5M, valibot 1.9M, effect 1.7M, Zod 3 1.2M, yup 124k" caption={<>Throughput on the moltar benchmark fixture (assertLoose: returns a boolean, unknown keys allowed) — higher is better (<a href="https://github.com/moltar/typescript-runtime-type-benchmarks/pull/2329">benchmark</a>)</>} />


# Ecosystem

import {
  ApiLibraries,
  FormIntegrations,
  ZodToX,
  XToZod,
  MockingLibraries,
  PoweredByZod,
  ZodUtilities,
} from "../components/ecosystem";

> **Note** — The Ecosystem section focuses on libraries that support Zod 4. If your library supports Zod 4, please submit a PR to add it. For libraries that work with Zod 3, refer to [v3.zod.dev](https://v3.zod.dev/?id=ecosystem).

There are a growing number of tools that are built atop or support Zod natively! If you've built a tool or library on top of Zod, let me know [on Twitter](https://x.com/colinhacks) or [start a Discussion](https://github.com/colinhacks/zod/discussions). I'll add it below and tweet it out.

## Resources

* [Total TypeScript Zod Tutorial](https://www.totaltypescript.com/tutorials/zod) by [@mattpocockuk](https://x.com/mattpocockuk)
* [Fixing TypeScript's Blindspot: Runtime Typechecking](https://www.youtube.com/watch?v=rY_XqfSHock) by [@jherr](https://x.com/jherr)
* [Validate Environment Variables With Zod](https://catalins.tech/validate-environment-variables-with-zod/) by [@catalinmpit](https://x.com/catalinmpit)

## API Libraries

<ApiLibraries />

## Form Integrations

<FormIntegrations />

## Zod to X

<ZodToX />

## X to Zod

<XToZod />

## Mocking Libraries

<MockingLibraries />

## Powered by Zod

<PoweredByZod />

## Zod Utilities

<ZodUtilities />


# Customizing errors

import { Tabs, Tab } from 'fumadocs-ui/components/tabs';

{/* ## `$ZodError` */}

In Zod, validation errors are surfaced as instances of the `z.core.$ZodError` class.

> The `ZodError` class in the `zod` package is a subclass that implements some additional convenience methods.

Instances of `$ZodError` contain an `.issues` array. Each issue contains a human-readable `message` and additional structured metadata about the issue.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    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'
    //   }
    // ]
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    import * as z from "zod/mini";

    const result = z.string().safeParse(12); // { success: false, error: z.core.$ZodError }
    result.error.issues;
    // [
    //   {
    //     expected: 'string',
    //     code: 'invalid_type',
    //     path: [],
    //     message: 'Invalid input'
    //   }
    // ]
    ```
  </Tab>
</Tabs>

{/* ## Customization */}

Every issue contains a `message` property with a human-readable error message. Error messages can be customized in a number of ways.

## The `error` param

Virtually every Zod API accepts an optional error message.

```ts
z.string("Not a string!");
```

This custom error will show up as the `message` property of any validation issues that originate from this schema.

```ts
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.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    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!");
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.string("Bad!");
    z.string().check(z.minLength(5, "Too short!"));
    z.uuid("Bad UUID!");
    z.iso.date("Bad date!");
    z.array(z.string(), "Bad array!");
    z.array(z.string()).check(z.minLength(5, "Too few items!"));
    z.set(z.string(), "Bad set!");
    ```
  </Tab>
</Tabs>

If you prefer, you can pass a params object with an `error` parameter instead.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    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!" });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    z.string({ error: "Bad!" });
    z.string().check(z.minLength(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()).check(z.minLength(5, { error: "Too few items!" }));
    z.set(z.string(), { error: "Bad set!" });
    ```
  </Tab>
</Tabs>

The `error` param optionally accepts a function. An error customization function is known as an **error map** in Zod terminology. The error map will run at parse time if a validation error occurs.

```ts
z.string({ error: ()=>`[${Date.now()}]: Validation failure.` });
```

The error map receives a context object you can use to customize the error message based on the validation issue.

```ts
z.string({
  error: (iss) => iss.input === undefined ? "Field is required." : "Invalid input."
});
```

For advanced cases, the `iss` object provides additional information you can use to customize the error.

```ts
z.string({
  error: (iss) => {
    iss.code; // the issue code
    iss.input; // the input data
    iss.inst; // the schema/check that originated this issue
    iss.schema; // the schema that owns this issue
    iss.path; // the path of the error
  },
});
```

Unlike `iss.inst`, `iss.schema` is always the schema, even when a check originated the issue. Use it to read the owning schema's [metadata](/metadata).

```ts
z.config({
  customError: (iss) => {
    const meta = iss.schema && z.globalRegistry.get(iss.schema);
    return `${meta?.title ?? "Field"} is invalid.`;
  },
});

z.string().min(5).meta({ title: "Password" }).safeParse("abc");
// => "Password is invalid."
```

Depending on the API you are using, there may be additional properties available. Use TypeScript's autocomplete to explore the available properties.

```ts
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`;
  },
});
```

Return `undefined` to avoid customizing the error message and fall back to the default message. (More specifically, Zod will yield control to the next error map in the [precedence chain](#error-precedence).) This is useful for selectively customizing certain error messages but not others.

```ts
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

To customize errors on a *per-parse* basis, pass an error map into the parse method:

```ts
const schema = z.string();

schema.parse(12, {
  error: iss => "per-parse custom error"
});
```

This has *lower precedence* than any schema-level custom messages.

```ts
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](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions) of all possible issue types. Use the `code` property to discriminate between them.

> For a breakdown of all Zod issue codes, see the [`zod/v4/core`](/packages/core#issue-types) documentation.

```ts
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

By default, Zod does not include input data in issues. This is to prevent unintentional logging of potentially sensitive input data. To include the input data in each issue, use the `reportInput` flag:

```ts
z.string().parse(12, {
  reportInput: true
})

// ZodError: [
//   {
//     "expected": "string",
//     "code": "invalid_type",
//     "input": 12, // 👀
//     "path": [],
//     "message": "Invalid input: expected string, received number"
//   }
// ]
```

## Global error customization

To specify a global error map, use `z.config()` to set Zod's `customError` configuration setting:

```ts
z.config({
  customError: (iss) => {
    return "globally modified error";
  },
});
```

Global error messages have *lower precedence* than schema-level or per-parse error messages.

The `iss` object is a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions) of all possible issue types. Use the `code` property to discriminate between them.

> For a breakdown of all Zod issue codes, see the [`zod/v4/core`](/packages/core#issue-types) documentation.

```ts
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}`;
    }
    // ...
  },
});
```

## Internationalization

To support internationalization of error message, Zod provides several built-in **locales**. These are exported from the `zod/v4/core` package.

> **Note** — The regular `zod` library loads the `en` locale automatically. Zod Mini does not load any locale by default; instead all error messages default to `Invalid input`.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    import * as z from "zod";
    import { en } from "zod/locales"

    z.config(en());
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    import * as z from "zod/mini"
    import { en } from "zod/locales";

    z.config(en());
    ```
  </Tab>
</Tabs>

To lazily load a locale, consider dynamic imports:

```ts
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");
```

All locales are also exported as `z.locales`. Rollup and Webpack tree-shake that down to the ones you use; esbuild ([evanw/esbuild#1420](https://github.com/evanw/esbuild/issues/1420)) and Turbopack don't.

With your own translation function, the `error` param is called during `.parse()`, not when the schema is constructed — a schema defined once picks up the current language on every parse. To translate at render time, translate the issue itself: `code` is the key, and the issue's other properties are the interpolation values.

```ts
z.string().min(5, { error: (iss) => t("too_short", iss) }); // at parse time
result.error?.issues.map((iss) => t(iss.code, iss)); // at render time
```

### Locales

The following locales are available:

* `ar` — Arabic
* `az` — Azerbaijani
* `be` — Belarusian
* `bg` — Bulgarian
* `bn` — Bengali
* `ca` — Catalan
* `ckb` — Kurdish (Central)
* `cs` — Czech
* `da` — Danish
* `de` — German
* `el` — Greek
* `en` — English
* `eo` — Esperanto
* `es` — Spanish
* `fa` — Farsi
* `fi` — Finnish
* `fr` — French
* `frCA` — Canadian French
* `gu` — Gujarati
* `he` — Hebrew
* `hi` — Hindi
* `hr` — Croatian
* `hu` — Hungarian
* `hy` — Armenian
* `id` — Indonesian
* `is` — Icelandic
* `it` — Italian
* `ja` — Japanese
* `ka` — Georgian
* `km` — Khmer
* `kn` — Kannada
* `ko` — Korean
* `lt` — Lithuanian
* `mk` — Macedonian
* `ms` — Malay
* `ne` — Nepali
* `nl` — Dutch
* `nn` — Norwegian Nynorsk
* `no` — Norwegian
* `ota` — Türkî
* `ps` — Pashto
* `pl` — Polish
* `pt` — Portuguese
* `ptBR` — Brazilian Portuguese
* `ro` — Romanian
* `ru` — Russian
* `sk` — Slovak
* `sl` — Slovenian
* `sv` — Swedish
* `ta` — Tamil
* `tg` — Tajik
* `th` — Thai
* `tk` — Türkmen
* `tr` — Türkçe
* `uk` — Ukrainian
* `ur` — Urdu
* `uz` — Uzbek
* `vi` — Tiếng Việt
* `zhCN` — Simplified Chinese
* `zhTW` — Traditional Chinese
* `yo` — Yorùbá

## Error precedence

Below is a quick reference for determining error precedence: if multiple error customizations have been defined, which one takes priority? From *highest to lowest* priority:

1. **Check-level error** — An error defined on the individual check that failed.

```ts
z.string().min(5, "Too short!");
```

2. **Schema-level error** — Any error message "hard coded" into a schema definition. It covers issues raised by the schema's own checks, so it is the fallback when the check defines no error of its own.

```ts
z.string("Invalid name").safeParse(12);          // => "Invalid name"
z.string("Invalid name").min(5).safeParse("ab"); // => "Invalid name"
```

3. **Per-parse error** — A custom error map passed into the `.parse()` method.

```ts
z.string().parse(12, {
  error: (iss) => "My custom error"
});
```

4. **Global error map** — A custom error map passed into `z.config()`.

```ts
z.config({
  customError: (iss) => "My custom error"
});
```

5. **Locale error map** — A custom error map passed into `z.config()`.

```ts
import { en } from "zod/locales";

z.config(en());
```


# Formatting errors

import { Callout } from "fumadocs-ui/components/callout";
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';

Zod emphasizes *completeness* and *correctness* in its error reporting. In many cases, it's helpful to convert the `$ZodError` to a more useful format. Zod provides some utilities for this.

Consider this simple object schema.

```ts
import * as z from "zod";

const schema = z.strictObject({
  username: z.string(),
  favoriteNumbers: z.array(z.number()),
});
```

Attempting to parse this invalid data results in an error containing three issues.

```ts
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()`

To convert ("treeify") this error into a nested object, use `z.treeifyError()`.

```ts
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' ]
        }
      ]
    }
  }
}
```

The result is a nested structure that mirrors the schema itself. You can easily access the errors that occurred at a particular path. The `errors` field contains the error messages at a given path, and the special properties `properties` and `items` let you traverse deeper into the tree.

```ts
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()`

The `z.prettifyError()` provides a human-readable string representation of the error.

```ts
const pretty = z.prettifyError(result.error);
```

This returns the following string:

```
✖ Unrecognized key: "extraKey"
✖ Invalid input: expected string, received number
  → at username
✖ Invalid input: expected number, received string
  → at favoriteNumbers[1]
```

## `z.formatError()`

<Callout type="warn">
  Deprecated. Use `z.treeifyError()` instead.
</Callout>

<Accordions>
  <Accordion title="Show docs">
    To convert the error into a nested object:

    ```ts
    const formatted = z.formatError(result.error);

    // returns:
    {
     _errors: [ 'Unrecognized key: "extraKey"' ],
     username: { _errors: [ 'Invalid input: expected string, received number' ] },
     favoriteNumbers: {
       '1': { _errors: [ 'Invalid input: expected number, received string' ] },
       _errors: []
     }
    }
    ```

    The result is a nested structure that mirrors the schema itself. You can easily access the errors that occurred at a particular path.

    ```ts
    formatted?.username?._errors;
    // => ["Invalid input: expected string, received number"]

    formatted?.favoriteNumbers?.[1]?._errors;
    // => ["Invalid input: expected number, received string"]
    ```

    > Be sure to use optional chaining (`?.`) to avoid errors when accessing nested properties.
  </Accordion>
</Accordions>

## `z.flattenError()`

While `z.treeifyError()` is useful for traversing a potentially complex nested structure, the majority of schemas are *flat*—just one level deep. In this case, use `z.flattenError()` to retrieve a clean, shallow error object.

```ts
const flattened = z.flattenError(result.error);
// { errors: string[], properties: { [key: string]: string[] } }

{
  formErrors: [ 'Unrecognized key: "extraKey"' ],
  fieldErrors: {
    username: [ 'Invalid input: expected string, received number' ],
    favoriteNumbers: [ 'Invalid input: expected number, received string' ]
  }
}
```

The `formErrors` array contains any top-level errors (where `path` is `[]`). The `fieldErrors` object provides an array of errors for each field in the schema.

```ts
flattened.fieldErrors.username; // => [ 'Invalid input: expected string, received number' ]
flattened.fieldErrors.favoriteNumbers; // => [ 'Invalid input: expected number, received string' ]
```


# Intro

import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { Tabs } from 'fumadocs-ui/components/tabs';

import { Platinum } from '../components/platinum';
import { Gold } from '../components/gold';
import { Silver } from '../components/silver';
import { Bronze } from '../components/bronze';
import { HeroLogo } from '../components/hero-logo';
import { BlogPill } from '../components/blog-feed';

<div className="flex flex-col items-stretch text-center">
  <BlogPill />

  <HeroLogo />

  <h1 className="text-3xl font-bold">Zod</h1>
  <p className="mt-2 mb-1">TypeScript-first schema validation with static type inference<br />by <a href="https://x.com/colinhacks">@colinhacks</a> </p>

  <br />

  <div className="flex flex-row flex-wrap justify-center gap-1 py-2">
    <a className="border-none" href="https://github.com/colinhacks/zod/actions?query=branch%3Amain">
      <img className="h-[20px] m-0!" src="https://github.com/colinhacks/zod/actions/workflows/test.yml/badge.svg?event=push&branch=main" alt="Zod CI status" />
    </a>

    <a className="border-none" href="https://twitter.com/colinhacks" rel="nofollow">
      <img className="h-[20px] m-0!" src="https://img.shields.io/badge/created%20by-@colinhacks-4BBAAB.svg" alt="Created by Colin McDonnell" />
    </a>

    <a className="border-none" href="https://opensource.org/licenses/MIT" rel="nofollow">
      <img className="h-[20px] m-0!" src="https://img.shields.io/github/license/colinhacks/zod" alt="License" />
    </a>

    <a className="border-none" href="https://www.npmjs.com/package/zod" rel="nofollow">
      <img className="h-[20px] m-0!" src="https://img.shields.io/npm/dw/zod.svg" alt="npm" />
    </a>

    <a className="border-none" href="https://github.com/colinhacks/zod" rel="nofollow">
      <img className="h-[20px] m-0!" src="https://img.shields.io/github/stars/colinhacks/zod" alt="stars" />
    </a>
  </div>

  <div className="flex flex-row justify-center gap-1">
    <a href="https://zod.dev">Website</a>
    <span>  •  </span>
    <a href="https://discord.gg/RcG33DQJdf">Discord</a>
    <span>  •  </span>
    <a href="https://twitter.com/colinhacks">𝕏</a>
    <span>  •  </span>
    <a href="https://bsky.app/profile/zod.dev">Bluesky</a>

    <br />
  </div>
</div>

<br />

{/* <ParamField path="param" type="string">
  An example of a parameter field
  </ParamField>

  <ParamField query="filter" type="string" default="none" required>
  The filtering command used to sort through the users
  </ParamField>

  <ParamField body="user_age" type="integer" default="0" required>
  The age of the user. Cannot be less than 0
  </ParamField> */}

<div className="mt-5 font-gray-100 mx-auto text-center pt-12">
  <span className="">
    Zod 4 is stable. Read the <a rel="noopener noreferrer" href="/v4" alt="zod 4 release notes">release notes</a> and <a rel="noopener noreferrer" href="/v4/changelog" alt="zod 4 migration guide">migration guide</a>.
  </span>
</div>

<br />

<br />

<br />

## Introduction

Zod is a TypeScript-first validation library. Using Zod, you can define *schemas* you can use to validate data, from a simple `string` to a complex nested object.

```ts
import * as z from "zod";

const User = z.object({
  name: z.string(),
});

// some untrusted data...
const input = { /* stuff */ };

// the parsed result is validated and type safe!
const data = User.parse(input);

// 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

```sh
npm install zod
```

> Zod is also available as `@zod/zod` on [jsr.io](https://jsr.io/@zod/zod).

Zod provides an MCP server that can be used by agents to search Zod's docs. To add to your editor, follow [these instructions](https://share.inkeep.com/zod/mcp). Zod also provides an [llms.txt](https://zod.dev/llms.txt) file.

## Requirements

Zod is tested against *TypeScript v5.5* and later. Older versions may work but are not officially supported.

### `"strict"`

You must enable `strict` mode in your `tsconfig.json`. This is a best practice for all TypeScript projects.

```ts
// tsconfig.json
{
  // ...
  "compilerOptions": {
    // ...
    "strict": true
  }
}
```

## Ecosystem

Zod has a thriving ecosystem of libraries, tools, and integrations. Refer to the [Ecosystem page](/ecosystem) for a complete list of libraries that support Zod or are built on top of it.

* [Resources](/ecosystem?id=resources)
* [API Libraries](/ecosystem?id=api-libraries)
* [Form Integrations](/ecosystem?id=form-integrations)
* [Zod to X](/ecosystem?id=zod-to-x)
* [X to Zod](/ecosystem?id=x-to-zod)
* [Mocking Libraries](/ecosystem?id=mocking-libraries)
* [Powered by Zod](/ecosystem?id=powered-by-zod)

I also contribute to the following projects, which I'd like to highlight:

* [tRPC](https://trpc.io) - End-to-end typesafe APIs, with support for Zod schemas
* [React Hook Form](https://react-hook-form.com) - Hook-based form validation with a [Zod resolver](https://react-hook-form.com/docs/useform#resolver)
* [zshy](https://github.com/colinhacks/zshy) - Originally created as Zod's internal build tool. Bundler-free, batteries-included build tool for TypeScript libraries. Powered by `tsc`.

## Sponsors

Sponsorship at any level is appreciated and encouraged. If you built a paid product using Zod, consider one of the [corporate tiers](https://github.com/sponsors/colinhacks).

### Platinum

<Platinum />

<br />

### Gold

<Gold />

<br />

### Silver

<Silver />

<br />

### Bronze

<Bronze />

<br />


# JSON Schema

import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
import { Callout } from "fumadocs-ui/components/callout"
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';

<Callout icon={'💎'}>
  Introduced in `zod@4.0`, Zod supports native [JSON Schema](https://json-schema.org/) conversion. JSON Schema is a standard for describing the structure of JSON (with JSON). It's widely used in [OpenAPI](https://www.openapis.org/) definitions and defining [structured outputs](https://platform.openai.com/docs/guides/structured-outputs?api-mode=chat) for AI.
</Callout>

## `z.fromJSONSchema()`

<Callout type="warn">
  **Experimental** — The `z.fromJSONSchema()` function is experimental and is not considered part of Zod's stable API. It is likely to undergo implementation changes in future releases.
</Callout>

Zod provides `z.fromJSONSchema()` to convert a JSON Schema into a Zod schema.

```ts
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()`

To convert a Zod schema to JSON Schema, use the `z.toJSONSchema()` function.

```ts
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,
// }
```

All schema & checks are converted to their closest JSON Schema equivalent. Some types have no analog and cannot be reasonably represented. See the [`unrepresentable`](#unrepresentable) section below for more information on handling these cases.

```ts
z.bigint(); // ❌
z.int64(); // ❌
z.symbol(); // ❌
z.undefined(); // ❌
z.void(); // ❌
z.date(); // ❌
z.map(); // ❌
z.set(); // ❌
z.transform(); // ❌
z.nan(); // ❌
z.custom(); // ❌
z.number().multipleOf(0); // ❌ the divisor must be finite and non-zero
```

A second argument can be used to customize the conversion logic.

```ts
z.toJSONSchema(schema, {
  // ...params
})
```

Below is a quick reference for each supported parameter. Each one is explained in more detail below.

```ts
interface ToJSONSchemaParams {
  /** The JSON Schema version to target.
   * - `"draft-2020-12"` — Default. JSON Schema Draft 2020-12
   * - `"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. Unrepresentable types throw an error
   * - `"any"` — Unrepresentable types become `{}`
   * - A function — returns the JSON Schema to use, or `"any"`/`"throw"` */
  unrepresentable?:
    | "throw"
    | "any"
    | ((ctx: {
        zodSchema: $ZodTypes;
        path: (string | number)[];
        message: string;
      }) => JSONSchema | "throw" | "any" | undefined);

  /** How to handle cycles.
   * - `"ref"` — Default. Cycles will be broken using $defs
   * - `"throw"` — Cycles will throw an error if encountered */
  cycles?: "ref" | "throw";

  /* How to handle reused schemas.
   * - `"inline"` — Default. Reused schemas will be inlined
   * - `"ref"` — Reused schemas will be extracted as $defs */
  reused?: "ref" | "inline";

  /** A function used to convert `id` values to URIs to be used in *external* $refs.
   *
   * Default is `(id) => id`.
   */
  uri?: (id: string) => string;
}
```

### `io`

Some schema types have different input and output types, e.g. `ZodPipe`, `ZodDefault`, and coerced primitives. By default, the result of `z.toJSONSchema` represents the *output type*; use `"io": "input"` to extract the input type instead.

```ts
const mySchema = z.string().transform(val => val.length).pipe(z.number());
// ZodPipe

const jsonSchema = z.toJSONSchema(mySchema); 
// => { type: "number" }

const jsonSchema = z.toJSONSchema(mySchema, { io: "input" }); 
// => { type: "string" }
```

### `target`

To set the target JSON Schema version, use the `target` parameter. By default, Zod will target Draft 2020-12.

```ts
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`

> If you haven't already, read through the [Metadata and registries](/metadata) page for context on storing metadata in Zod.

In Zod, metadata is stored in registries. Zod exports a global registry `z.globalRegistry` that can be used to store common metadata fields like `id`, `title`, `description`, and `examples`.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    import * as z from "zod";

    // `.meta()` is a convenience method for registering a schema in `z.globalRegistry`
    const emailSchema = z.string().meta({ 
      title: "Email address",
      description: "Your email address",
    });

    z.toJSONSchema(emailSchema);
    // => { type: "string", title: "Email address", description: "Your email address", ... } 
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    import * as z from "zod";

    // `.meta()` is a convenience method for registering a schema in `z.globalRegistry`
    const emailSchema = z.string().register(z.globalRegistry, { 
      title: "Email address",
      description: "Your email address",
    });

    z.toJSONSchema(emailSchema);
    // => { type: "string", title: "Email address", description: "Your email address", ... } 
    ```
  </Tab>
</Tabs>

All metadata fields get copied into the resulting JSON Schema.

```ts
const schema = z.string().meta({
  whatever: 1234
});

z.toJSONSchema(schema);
// => { type: "string", whatever: 1234 }
```

Metadata takes precedence over the keywords Zod generates.

```ts
z.toJSONSchema(z.string().meta({ type: "number" }));
// => { type: "number" }

z.toJSONSchema(z.date().meta({ type: "string", format: "date-time" }), { unrepresentable: "any" });
// => { type: "string", format: "date-time" }
```

To drop metadata entirely, pass an empty registry: `z.toJSONSchema(schema, { metadata: z.registry() })`. To modify a generated schema, use [`override`](#override).

### `unrepresentable`

The following APIs are not representable in JSON Schema. By default, Zod will throw an error if they are encountered. It is unsound to attempt a conversion to JSON Schema; you should modify your schemas  as they have no equivalent in JSON. An error will be thrown if any of these are encountered.

```ts
z.bigint(); // ❌
z.int64(); // ❌
z.symbol(); // ❌
z.undefined(); // ❌
z.void(); // ❌
z.date(); // ❌
z.map(); // ❌
z.set(); // ❌
z.transform(); // ❌
z.nan(); // ❌
z.custom(); // ❌
z.number().multipleOf(0); // ❌ the divisor must be finite and non-zero
```

By default, Zod will throw an error if any of these are encountered.

```ts
z.toJSONSchema(z.bigint());
// => throws Error
```

You can change this behavior by setting the `unrepresentable` option to `"any"`. This will convert any unrepresentable type to `{}` (the equivalent of `unknown` in JSON Schema), and drop an unrepresentable check from the schema that carries it.

```ts
z.toJSONSchema(z.bigint(), { unrepresentable: "any" });
// => {}

z.toJSONSchema(z.number().multipleOf(0), { unrepresentable: "any" });
// => { type: "number" }
```

To decide case by case, pass a function instead. It's called for each unrepresentable schema encountered. Return a JSON Schema to use in its place, `"any"`, or `"throw"` (the default when you return nothing).

```ts
z.toJSONSchema(z.object({ createdAt: z.date(), id: z.bigint() }), {
  unrepresentable: ({ zodSchema }) =>
    zodSchema._zod.def.type === "date" ? { type: "string", format: "date-time" } : "throw",
});
// => throws Error (BigInt cannot be represented in JSON Schema)
```

The handler also receives the `path` of the schema and the `message` Zod would have thrown, and any error it throws propagates, so you can report unrepresentable types in your own words.

```ts
z.toJSONSchema(schema, {
  unrepresentable: ({ path, message }) => {
    throw new Error(`${message} (at /${path.join("/")})`);
  },
});
```

Two schemas can share a `zodSchema` but not a `message` — an `undefined` member and a `bigint` member of the same literal both arrive as the literal — so branch on `message` to tell those apart.

A default *value* that can't be serialized to JSON goes through the same handler, as the `.default()` schema. Under `"any"` the default is dropped and the rest of the schema is emitted as usual; return a JSON Schema to supply your own.

```ts
z.toJSONSchema(z.bigint().default(0n), {
  unrepresentable: ({ zodSchema }) => {
    const def = zodSchema._zod.def;
    if (def.type === "bigint") return { type: "integer", format: "int64" };
    if (def.type === "default") return { default: String(def.defaultValue) };
    return "throw";
  },
});
// => { type: "integer", format: "int64", default: "0" }
```

Returning a JSON Schema for a literal replaces the whole literal, dropping its representable members: `z.literal(["a", 1n])` becomes just what you returned, and `"a"` is gone. Return `"any"` instead to keep the existing per-value behavior.

### `cycles`

How to handle cycles. If a cycle is encountered as `z.toJSONSchema()` traverses the schema, it will be represented using `$ref`.

```ts
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,
// }
```

If instead you want to throw an error, set the `cycles` option to `"throw"`.

```ts
z.toJSONSchema(User, { cycles: "throw" });
// => throws Error
```

### `reused`

How to handle schemas that occur multiple times in the same schema. By default, Zod will inline these schemas.

```ts
const name = z.string();
const User = z.object({
  firstName: name,
  lastName: name,
});

z.toJSONSchema(User);
// => {
//   type: 'object',
//   properties: { 
//     firstName: { type: 'string' }, 
//     lastName: { type: 'string' } 
//   },
//   required: [ 'firstName', 'lastName' ],
//   additionalProperties: false,
// }
```

Instead you can set the `reused` option to `"ref"` to extract these schemas into `$defs`.

```ts
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`

To define some custom override logic, use `override`. The provided callback has access to the original Zod schema and the default JSON Schema. *This function should directly modify `ctx.jsonSchema`.*

```ts
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";
  }
});
```

Note that unrepresentable types will throw an `Error` before this function is called. To represent one of them, use [`unrepresentable`](#unrepresentable) — a handler there substitutes a type without disabling the error for the others. Setting `unrepresentable: "any"` alongside `override` also works, but erases every unrepresentable type.

```ts
// 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

Below are additional details regarding Zod's JSON Schema conversion logic.

### String formats

Zod converts the following schema types to the equivalent JSON Schema `format`:

```ts
// 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.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" }
```

The `local: true` and `precision: -1` variants of `z.iso.datetime()` fall back to `pattern`, since RFC 3339 `date-time` requires both an offset and seconds.

These schemas are supported via `contentEncoding`:

```ts
z.base64(); // => { type: "string", contentEncoding: "base64" }
```

All other string formats are supported via `pattern`:

```ts
z.iso.time();
z.base64url();
z.cuid();
z.emoji();
z.nanoid();
z.cuid2();
z.ulid();
z.cidrv4();
z.cidrv6();
z.mac();
z.currencyCode();
```

### Numeric types

Zod converts the following numeric types to JSON Schema:

```ts
// 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 contain `additionalProperties: "false"`. This is an accurate representation of Zod's default behavior, as plain `z.object()` schema strip additional properties.

```ts
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,
// }
```

When converting to JSON Schema in `"input"` mode, `additionalProperties` is not set. See the [`io` docs](#io) for more information.

```ts
import * as z from "zod";

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

z.toJSONSchema(schema, { io: "input" });
// => {
//   type: 'object',
//   properties: { name: { type: 'string' }, age: { type: 'number' } },
//   required: [ 'name', 'age' ],
// }
```

By contrast:

* `z.looseObject()` will *never* set `additionalProperties: false`
* `z.strictObject()` will *always* set `additionalProperties: false`

### File schemas

Zod converts `z.file()` to the following OpenAPI-friendly schema:

```ts
z.file();
// => { type: "string", format: "binary", contentEncoding: "binary" }
```

Size and MIME checks are also represented:

```ts
z.file().min(1).max(1024 * 1024).mime("image/png");
// => {
//   type: "string",
//   format: "binary",
//   contentEncoding: "binary",
//   contentMediaType: "image/png",
//   minLength: 1,
//   maxLength: 1048576,
// }
```

### Nullability

Zod converts `z.null()` to `{ type: "null" }` in JSON Schema.

```ts
z.null();
// => { type: "null" }
```

Note that `z.undefined()` is unrepresentable in JSON Schema (see [below](#unrepresentable)).

Similarly, `nullable` adds `"null"` to the set of accepted types. Inner schemas that can't be written as a bare type fall back to `anyOf`:

```ts
z.nullable(z.string());
// => { type: ["string", "null"] }

z.nullable(z.string().min(5));
// => { anyOf: [{ type: "string", minLength: 5 }, { type: "null" }] }
```

Optional schemas are represented as-is, though they are decorated with an `optional` annotation.

```ts
z.optional(z.string());
// => { type: "string" }
```

{/* ### Pipes

  Pipes contain an input and an output schema. Zod uses the *output schema* for JSON Schema conversion. */}

## Registries

Passing a schema into `z.toJSONSchema()` will return a *self-contained* JSON Schema.

In other cases, you may have a set of Zod schemas you'd like to represent using multiple interlinked JSON Schemas, perhaps to write to `.json` files and serve from a web server.

```ts
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"});
```

To achieve this, you can pass a [registry](/metadata#registries) into `z.toJSONSchema()`.

> **Important** — All schemas should have a registered `id` property in the registry! Any schemas without an `id` will be ignored.

```ts
z.toJSONSchema(z.globalRegistry);
// => {
//   schemas: {
//     User: {
//       id: 'User',
//       type: 'object',
//       properties: {
//         name: { type: 'string' },
//         posts: { type: 'array', items: { '$ref': 'Post' } }
//       },
//       required: [ 'name', 'posts' ],
//       additionalProperties: false,
//     },
//     Post: {
//       id: 'Post',
//       type: 'object',
//       properties: {
//         title: { type: 'string' },
//         content: { type: 'string' },
//         author: { '$ref': 'User' }
//       },
//       required: [ 'title', 'content', 'author' ],
//       additionalProperties: false,
//     }
//   }
// }
```

By default, the `$ref` URIs are simple relative paths like `"User"`. To make these absolute URIs, use the `uri` option. This expects a function that converts an `id` to a fully-qualified URI.

```ts
z.toJSONSchema(z.globalRegistry, {
  uri: (id) => `https://example.com/${id}.json`
});
// => {
//   schemas: {
//     User: {
//       id: 'User',
//       type: 'object',
//       properties: {
//         name: { type: 'string' },
//         posts: {
//           type: 'array',
//           items: { '$ref': 'https://example.com/Post.json' }
//         }
//       },
//       required: [ 'name', 'posts' ],
//       additionalProperties: false,
//     },
//     Post: {
//       id: 'Post',
//       type: 'object',
//       properties: {
//         title: { type: 'string' },
//         content: { type: 'string' },
//         author: { '$ref': 'https://example.com/User.json' }
//       },
//       required: [ 'title', 'content', 'author' ],
//       additionalProperties: false,
//     }
//   }
// }
```


# For library authors

import { Callout } from "fumadocs-ui/components/callout";

This page is primarily intended for consumption by *library authors* who are building tooling on top of Zod.

> If you are a library author and think this page should include some additional guidance, please open an issue!

<Callout>
  Zod 3 is functionally end-of-life. It still receives security fixes and unambiguous bug fixes, but no new features will land on `zod@3.x`. For any new library — or any new major version of an existing library — target Zod 4 only. See [How to support Zod 3 alongside Zod 4?](#how-to-support-zod-3-alongside-zod-4) for the dual-support pattern, intended for maintainers extending existing libraries.
</Callout>

## Do I need to depend on Zod?

First things first, make sure you need to depend on Zod at all.

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](https://standardschema.dev/). It's a shared interface implemented by most popular validation libraries in the TypeScript ecosystem (see the [full list](https://standardschema.dev/#what-schema-libraries-implement-the-spec)), including Zod.

This 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".

```json
// package.json
{
  // ...
  "peerDependencies": {
    "zod": "^4.0.0"
  }
}
```

During development, you need to meet your own peer dependency requirement, so add `"zod"` to your `"devDependencies"` as well.

```ts
// package.json
{
  "peerDependencies": {
    "zod": "^4.0.0"
  },
  "devDependencies": {
    "zod": "^4.0.0"
  }
}
```

If you're extending an existing library and wish to keep supporting Zod 3 users, see [How to support Zod 3 alongside Zod 4?](#how-to-support-zod-3-alongside-zod-4) for the wider peer dependency range.

## Which subpaths should I import from?

Import the Zod 4 core package from the `"zod/v4/core"` subpath:

```ts
import * as z4 from "zod/v4/core";
```

Think of this subpath as a "permalink" to Zod 4. It will remain available forever, even across future major versions of the `zod` package. The only other permalink subpath is `"zod/v3"`, which you only need if you're [also supporting Zod 3](#how-to-support-zod-3-alongside-zod-4).

You generally shouldn't 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 exports 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. This is extremely discouraged. Use `"zod/v4/core"` instead, which exports the `$`-prefixed subclasses that are extended by Zod Classic and Zod Mini. The internals of the classic & mini subclasses are identical; they only differ in which helper methods they implement.

For full context on this versioning approach, see the [Versioning in Zod 4](https://github.com/colinhacks/zod/issues/4371) writeup.

## How to support Zod 3 alongside Zod 4?

<Callout>
  This section is for maintainers of existing libraries with Zod 3 users. For new libraries (or new major versions of existing libraries), support `^4.0.0` only — Zod 3 is functionally end-of-life and won't receive new features.
</Callout>

If you maintain a library with existing Zod 3 users, you can extend it to cover Zod 4 without dropping them. Widen your peer dependency to span both ranges — the `"zod/v4"` subpath is available starting in `3.25.0`:

```json
// package.json
{
  // ...
  "peerDependencies": {
    "zod": "^3.25.0 || ^4.0.0"
  }
}
```

This doesn't require a new major version of your library. Bumping the peer dependency requires your users to `npm upgrade zod`, but there were no breaking changes (in fact, no code changes at all) between `zod@3.24` and `zod@3.25`, so adding Zod 4 support can land in a minor. If your library is due for a major anyway, cutting one that drops Zod 3 and narrows the peer range to `^4.0.0` is the cleaner option.

Since `v3.25.0` the `zod` package contains copies of both Zod 3 and Zod 4 at their respective subpaths, so you can import both side by side:

```ts
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) {
  // ...
}
```

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.

```ts
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. As long as you follow this rule, both Zod and Zod Mini will work automatically.

```ts
// 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. This function can accept both Zod and Zod Mini schemas.

```ts
// 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](/packages/core) page for more information on the contents of the core sub-library.

{/* ### Future proofing

  To future-proof your library, your code should always allow for new schema and check classes to be added in the future. The addition of a new schema type is *not* considered a breaking change. 

  One common pattern when introspecting Zod schemas is to write a switch statement over the set of first-party schema types:

  ```ts
  const schema = {} as z.$ZodTypes;
  const def = schema._zod.def;
  switch (def.type) {
  case "string":
    // ...
    break;
  case "object":
    // ...
    break;
  default:
    console.warn(`Unknown schema type: ${def.type}`);
    // reasonable fallback behavior
  }
  ```

  To future-proof this code, your `default` case should probably not throw an error. Instead, it should print an informative error and fall back to some reasonable behavior. If instead you `throw` an error in the default case, your library will be unusable if/when new schemas types are added in the future. Best to print a warning and treat it as a "no-op" (or some other reasonable fallback behavior). The same applies to unrecognized check types, string formats, etc.

  */}

## How to accept user-defined schemas?

Accepting user-defined schemas is the a fundamental operation for any library built on Zod. This section outlines the best practices for doing so.

When starting out, it may be tempting to write a function that accepts a Zod schema like this:

```ts
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`.

```ts
inferSchema(z.string());
// => $ZodType<string>
```

This approach loses type information, namely *which subclass* the input actually is (in this case, `ZodString`). That means you can't call any string-specific methods like `.min()` on the result of `inferSchema`. Instead, your generic parameter should extend the core Zod schema interface:

```ts
function inferSchema<T extends z4.$ZodType>(schema: T) {
  return schema;
}

inferSchema(z.string());
// => ZodString ✅
```

To constrain the input schema to a specific subclass:

```ts

import * as z4 from "zod/v4/core";

// only accepts object schemas
function inferSchema<T extends z4.$ZodObject>(schema: T) {
  return schema;
}
```

To constrain the inferred output type of the input schema:

```ts

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()); // ✅ 

inferSchema(z.number()); 
// ❌ The types of '_zod.output' are incompatible between these types. 
// // Type 'number' is not assignable to type 'string'
```

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.

```ts
function parseData<T extends z4.$ZodType>(data: unknown, schema: T): z4.output<T> {
  return z.parse(schema, data);
}

parseData("sup", z.string());
// => string
```


# Metadata and registries

import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
import { Callout } from "fumadocs-ui/components/callout"

{/* > Zod 4+ provides native `.toJSONSChema()` functionality that leverages registries to generate idiomatic JSON Schema from Zod. Refer to the [JSON SChema docs](/json-schema) page for more information. */}

It's often useful to associate a schema with some additional *metadata* for documentation, code generation, AI structured outputs, form validation, and other purposes.

## Registries

Metadata in Zod is handled via *registries*. Registries are collections of schemas, each associated with some *strongly-typed* metadata. To create a simple registry:

```ts
import * as z from "zod";

const myRegistry = z.registry<{ description: string }>();
```

To register, lookup, and remove schemas from this registry:

```ts
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 registry
```

TypeScript enforces that the metadata for each schema matches the registry's **metadata type**.

```ts
myRegistry.add(mySchema, { description: "A cool schema!" }); // ✅
myRegistry.add(mySchema, { description: 123 }); // ❌
```

> **Special 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()`

> **Note** — This method is special in that it does not return a new schema; instead, it returns the original schema. No other Zod method does this! That includes `.meta()` and `.describe()` (documented below) which return a new instance.

Schemas provide a `.register()` method to more conveniently add it to a registry.

```ts
const mySchema = z.string();

mySchema.register(myRegistry, { description: "A cool schema!" });
// => mySchema
```

This lets you define metadata "inline" in your schemas.

```ts
const mySchema = z.object({
  name: z.string().register(myRegistry, { description: "The user's name" }),
  age: z.number().register(myRegistry, { description: "The user's age" }),
})
```

<Callout>
  If a registry is defined without a metadata type, you can use it as a generic "collection", no metadata required.

  ```ts
  const myRegistry = z.registry();

  myRegistry.add(z.string());
  myRegistry.add(z.number());
  ```
</Callout>

## Metadata

### `z.globalRegistry`

For convenience, Zod provides a global registry (`z.globalRegistry`) that can be used to store metadata for JSON Schema generation or other purposes. It accepts the following metadata:

```ts
export interface GlobalMeta {
  id?: string ;
  title?: string ;
  description?: string;
  deprecated?: boolean;
  [k: string]: unknown;
}
```

To register some metadata in `z.globalRegistry` for a schema:

```ts
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"]
});
```

To globally augment the `GlobalMeta` interface, use [*declaration merging*](https://www.typescriptlang.org/docs/handbook/declaration-merging.html). Add the following anywhere in your codebase. Creating a `zod.d.ts` file in your project root is a common convention.

```ts
declare module "zod" {
  interface GlobalMeta {
    // add new fields here
    examples?: unknown[];
  }
}

// forces TypeScript to consider the file a module
export {}
```

### `.meta()`

For a more convenient approach, use the `.meta()` method to register a schema in `z.globalRegistry`.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const emailSchema = z.email().meta({ 
      id: "email_address",
      title: "Email address",
      description: "Please enter a valid email address",
    });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const emailSchema = z.email().check(
      z.meta({ 
        id: "email_address",
        title: "Email address",
        description: "Please enter a valid email address",
      })
    );
    ```
  </Tab>
</Tabs>

Calling `.meta()` without an argument will *retrieve* the metadata for a schema.

```ts
emailSchema.meta();
// => { id: "email_address", title: "Email address", ... }
```

Metadata is associated with a *specific schema instance.* This is important to keep in mind, especially since Zod methods are immutable—they always return a new instance.

```ts
const A = z.string().meta({ description: "A cool string" });
A.meta(); // => { description: "A cool string" }

const B = A.refine(_ => true);
B.meta(); // => undefined
```

### `.describe()`

<Callout>
  The `.describe()` method remains available, but `.meta()` is the recommended approach.
</Callout>

The `.describe()` method is a shorthand for registering a schema in `z.globalRegistry` with just a `description` field.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
  <Tab value="Zod">
    ```ts
    const emailSchema = z.email();
    emailSchema.describe("An email address");

    // equivalent to
    emailSchema.meta({ description: "An email address" });
    ```
  </Tab>

  <Tab value="Zod Mini">
    ```ts
    const emailSchema = z.email().check(z.describe("An email address"));

    // equivalent to
    z.email().check(z.meta({ description: "An email address" }));
    ```
  </Tab>
</Tabs>

## Custom registries

You've already seen a simple example of a custom registry:

```ts
import * as z from "zod";

const myRegistry = z.registry<{ description: string };>();
```

Let's look at some more advanced patterns.

### Referencing inferred types

It's often valuable for the metadata type to reference the *inferred type* of a schema. For instance, you may want an `examples` field to contain examples of the schema's output.

```ts
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] });
```

The special symbol `z.$output` is a reference to the schemas inferred output type (`z.infer<typeof schema>`). Similarly you can use `z.$input` to reference the input type.

### Constraining schema types

Pass a second generic to `z.registry()` to constrain the schema types that can be added to a registry. This registry only accepts string schemas.

```ts
import * as z from "zod";

const myRegistry = z.registry<{ description: string }, z.ZodString>();

myRegistry.add(z.string(), { description: "A number" }); // ✅
myRegistry.add(z.number(), { description: "A number" }); // ❌ 
//             ^ 'ZodNumber' is not assignable to parameter of type 'ZodString' 
```


# Migration guide

import { Callout } from "fumadocs-ui/components/callout";
import { Tabs, Tab } from "fumadocs-ui/components/tabs";

This migration guide aims to list the breaking changes in Zod 4 in order of highest to lowest impact. To learn more about the performance enhancements and new features of Zod 4, read the [introductory post](/v4).

{/* To give the ecosystem time to migrate, Zod 4 will initially be published alongside Zod v3.25. To use Zod 4, upgrade to `zod@3.25.0` or later: */}

```
npm install zod@^4.0.0
```

{/* Zod 4 is available at the `"/v4"` subpath:

  ```ts
  import * as z from "zod";
  ``` */}

Many of Zod's behaviors and APIs have been made more intuitive and cohesive. The breaking changes described in this document often represent major quality-of-life improvements for Zod users. I strongly recommend reading this guide thoroughly.

<Callout>
  **Note** — Zod 3 exported a number of undocumented quasi-internal utility types and functions that are not considered part of the public API. Changes to those are not documented here.
</Callout>

<Callout>
  **Unofficial codemod** — A community-maintained codemod [`zod-v3-to-v4`](https://github.com/nicoespeon/zod-v3-to-v4) is available.
</Callout>

## Error customization

Zod 4 standardizes the APIs for error customization under a single, unified `error` param. Previously Zod's error customization APIs were fragmented and inconsistent. This is cleaned up in Zod 4.

### deprecates `message` parameter

Replaces `message` param with `error`. The old `message` parameter is still supported but deprecated.

<Tabs groupId="error-message" items={["Zod 4", "Zod 3"]} persist>
  <Tab value="Zod 4">
    ```ts
    z.string().min(5, { error: "Too short." });
    ```
  </Tab>

  <Tab value="Zod 3">
    ```ts
    z.string().min(5, { message: "Too short." });
    ```
  </Tab>
</Tabs>

### drops `invalid_type_error` and `required_error`

The `invalid_type_error` / `required_error` params have been dropped. These were hastily added years ago as a way to customize errors that was less verbose than `errorMap`. They came with all sorts of footguns (they can't be used in conjunction with `errorMap`) and do not align with Zod's actual issue codes (there is no `required` issue code).

These can now be cleanly represented with the new `error` parameter.

<Tabs groupId="error-type" items={["Zod 4", "Zod 3"]} persist>
  <Tab value="Zod 4">
    ```ts
    z.string({ 
      error: (issue) => issue.input === undefined 
        ? "This field is required" 
        : "Not a string" 
    });
    ```
  </Tab>

  <Tab value="Zod 3">
    ```ts
    z.string({ 
      required_error: "This field is required",
      invalid_type_error: "Not a string", 
    });
    ```
  </Tab>
</Tabs>

### drops `errorMap`

This is renamed to `error`.

Error maps can also now return a plain `string` (instead of `{message: string}`). They can also return `undefined`, which tells Zod to yield control to the next error map in the chain.

<Tabs groupId="error-map" items={["Zod 4", "Zod 3"]} persist>
  <Tab value="Zod 4">
    ```ts
    z.string().min(5, {
      error: (issue) => {
        if (issue.code === "too_small") {
          return `Value must be >${issue.minimum}`
        }
      },
    });
    ```
  </Tab>

  <Tab value="Zod 3">
    ```ts
    z.string({
      errorMap: (issue, ctx) => {
        if (issue.code === "too_small") {
          return { message: `Value must be >${issue.minimum}` };
        }
        return { message: ctx.defaultError };
      },
    });
    ```
  </Tab>
</Tabs>

{/* ## `.safeParse()` 

  For performance reasons, the errors returned by `.safeParse()` and `.safeParseAsync()` no longer extend `Error`. 

  ```ts
  const result = z.string().safeParse(12); 
  result.error! instanceof Error; // => false
  ```

  It is very slow to instantiate `Error` instances in JavaScript, as the initialization process snapshots the call stack. In the case of Zod's "safe" parse methods, it's expected that you will handle errors at the point of parsing, so instantiating a true `Error` object adds little value anyway. 

  > Pro tip: prefer `.safeParse()` over `try/catch` in performance-sensitive code.

  By contrast the errors thrown by `.parse()` and `.parseAsync()` still extend `Error`. Aside from the prototype difference, the error classes are identical.

  ```ts
  try {
  z.string().parse(12);
  } catch (err) {
  console.log(err instanceof Error); // => true
  }
  ```
  */}

## `ZodError`

{/* 
  ### changes to `.message` 

  Previously the `.message` property on `ZodError` was a JSON.stringified copy of the `.issues` array. This was redundant, confusing, and a bit of an abuse of the `.message` property. Also due to the [`Error` prototype changes](#safeparse) (and inconsistencies in how Node.js logs `Error` subclasses vs other objects) the logging of a multi-line `.message` property got a lot uglier:

  ```sh
  $ tsx index.ts
  ZodError {
  message: '[\n' +
    '  {\n' +
    '    "expected": "string",\n' +
    '    "code": "invalid_type",\n' +
    '    "path": [],\n' +
    '    "message": "Invalid input: expected string, received number"\n' +
    '  }\n' +
    ']'
  }
  ```


  For these reasons, the `.message` property is left empty and the `.issues` array is marked as enumerable. This keeps error logging consistent and pretty:

  ```sh
  $ tsx index.ts
  z.string().parse(234);

  ZodError {
  issues: [
    {
      expected: 'string',
      code: 'invalid_type',
      path: [],
      message: 'Invalid input: expected string, received number'
    }
  ]
  }
  ```

  Vitest uses special handling for `Error` subclasses that ignores enumerable properties.  */}

### updates issue formats

The issue formats have been dramatically streamlined.

```ts
import * as z from "zod"; // v4

type IssueFormats = 
  | z.core.$ZodIssueInvalidType
  | z.core.$ZodIssueTooBig
  | z.core.$ZodIssueTooSmall
  | z.core.$ZodIssueInvalidStringFormat
  | z.core.$ZodIssueNotMultipleOf
  | z.core.$ZodIssueUnrecognizedKeys
  | z.core.$ZodIssueInvalidValue
  | z.core.$ZodIssueInvalidUnion
  | z.core.$ZodIssueInvalidKey // new: used for z.record/z.map 
  | z.core.$ZodIssueInvalidElement // new: used for z.map/z.set
  | z.core.$ZodIssueCustom;
```

Below is the list of Zod 3 issues types and their Zod 4 equivalent:

```ts
import * as z from "zod"; // v3

export type IssueFormats =
  | z.ZodInvalidTypeIssue // ♻️ renamed to z.core.$ZodIssueInvalidType
  | z.ZodTooBigIssue  // ♻️ renamed to z.core.$ZodIssueTooBig
  | z.ZodTooSmallIssue // ♻️ renamed to z.core.$ZodIssueTooSmall
  | z.ZodInvalidStringIssue // ♻️ z.core.$ZodIssueInvalidStringFormat
  | z.ZodNotMultipleOfIssue // ♻️ renamed to z.core.$ZodIssueNotMultipleOf
  | z.ZodUnrecognizedKeysIssue // ♻️ renamed to z.core.$ZodIssueUnrecognizedKeys
  | z.ZodInvalidUnionIssue // ♻️ renamed to z.core.$ZodIssueInvalidUnion
  | z.ZodCustomIssue // ♻️ renamed to z.core.$ZodIssueCustom
  | z.ZodInvalidEnumValueIssue // ❌ merged in z.core.$ZodIssueInvalidValue
  | z.ZodInvalidLiteralIssue // ❌ merged into z.core.$ZodIssueInvalidValue
  | z.ZodInvalidUnionDiscriminatorIssue // ❌ throws an Error at schema creation time
  | z.ZodInvalidArgumentsIssue // ❌ z.function throws ZodError directly
  | z.ZodInvalidReturnTypeIssue // ❌ z.function throws ZodError directly
  | z.ZodInvalidDateIssue // ❌ merged into invalid_type
  | z.ZodInvalidIntersectionTypesIssue // ❌ removed (throws regular Error)
  | z.ZodNotFiniteIssue // ❌ infinite values no longer accepted (invalid_type)
```

While certain Zod 4 issue types have been merged, dropped, and modified, each issue remains structurally similar to Zod 3 counterpart (identical, in most cases). All issues still conform to the same base interface as Zod 3, so most common error handling logic will work without modification.

```ts
export interface $ZodIssueBase {
  readonly code?: string;
  readonly input?: unknown;
  readonly path: PropertyKey[];
  readonly message: string;
}
```

### changes error map precedence

The error map precedence has been changed to be more consistent. Specifically, an error map passed into `.parse()` *no longer* takes precedence over a schema-level error map.

```ts
const mySchema = z.string({ error: () => "Schema-level error" });

// in Zod 3
mySchema.parse(12, { error: () => "Contextual error" }); // => "Contextual error"

// in Zod 4
mySchema.parse(12, { error: () => "Contextual error" }); // => "Schema-level error"
```

### deprecates `.format()`

The `.format()` method on `ZodError` has been deprecated. Instead use the top-level `z.treeifyError()` function. Read the [Formatting errors docs](/error-formatting) for more information.

### deprecates `.flatten()`

The `.flatten()` method on `ZodError` has also been deprecated. Instead use the top-level `z.treeifyError()` function. Read the [Formatting errors docs](/error-formatting) for more information.

### drops `.formErrors`

This API was identical to `.flatten()`. It exists for historical reasons and isn't documented.

### drops `.errors`

This API was an alias for `.issues` in Zod v3 but has been removed. Use `.issues` instead.

### deprecates `.addIssue()` and `.addIssues()`

Directly push to `err.issues` array instead, if necessary.

```ts
myError.issues.push({ 
  // new issue
});
```

{/* ## `.and()` dropped

  The `.and()` method on `ZodType` has been dropped in favor of `z.intersection(A, B)`. Not only is this method rarely used, there are few good reasons to use intersections at all. The `.and()` API prevented bundlers from treeshaking `ZodIntersection`, a fairly large and complex class. 

  ```ts
  z.object({ a: z.string() }).and(z.object({ b: z.number() })); // ❌

  // use z.intersection
  z.intersection(z.object({ a: z.string() }), z.object({ b: z.number() })); // ✅
  // or .extend() when possible
  z.object({ a: z.string() }).extend(z.object({ b: z.number() })); // ✅
  ``` */}

## `z.number()`

### no infinite values

`POSITIVE_INFINITY` and `NEGATIVE_INFINITY` are no longer considered valid values for `z.number()`.

### `.safe()` no longer accepts floats

In Zod 3, `z.number().safe()` is deprecated. It now behaves identically to `.int()` (see below). Importantly, that means it no longer accepts floats.

### `.int()` accepts safe integers only

The `z.number().int()` API no longer accepts unsafe integers (outside the range of `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER`). Using integers out of this range causes spontaneous rounding errors. (Also: You should switch to `z.int()`.)

## `z.string()` updates

### deprecates `.email()` etc

String formats are now represented as *subclasses* of `ZodString`, instead of simple internal refinements. As such, these APIs have been moved to the top-level `z` namespace. Top-level APIs are also less verbose and more tree-shakable.

```ts
z.email();
z.uuid();
z.url();
z.emoji();         // validates a single emoji character
z.base64();
z.base64url();
z.nanoid();
z.cuid();
z.cuid2();
z.ulid();
z.ipv4();
z.ipv6();
z.cidrv4();          // ip range
z.cidrv6();          // ip range
z.iso.date();
z.iso.time();
z.iso.datetime();
z.iso.duration();
```

The method forms (`z.string().email()`) still exist and work as before, but are now deprecated.

```ts
z.string().email(); // ❌ deprecated
z.email(); // ✅ 
```

### stricter `.uuid()`

The `z.uuid()` now validates UUIDs more strictly against the RFC 9562/4122 specification; specifically, the variant bits must be `10` per the spec. For a more permissive "UUID-like" validator, use `z.guid()`.

```ts
z.uuid(); // RFC 9562/4122 compliant UUID
z.guid(); // any 8-4-4-4-12 hex pattern
```

### no padding in `.base64url()`

Padding is no longer allowed in `z.base64url()` (formerly `z.string().base64url()`). Generally it's desirable for base64url strings to be unpadded and URL-safe.

### drops `z.string().ip()`

This has been replaced with separate `.ipv4()` and `.ipv6()` methods. Use `z.union()` to combine them if you need to accept both.

```ts
z.string().ip() // ❌
z.ipv4() // ✅
z.ipv6() // ✅
```

### updates `z.string().ipv6()`

Validation now happens using the `new URL()` constructor, which is far more robust than the old regular expression approach. Some invalid values that passed validation previously may now fail.

### drops `z.string().cidr()`

Similarly, this has been replaced with separate `.cidrv4()` and `.cidrv6()` methods. Use `z.union()` to combine them if you need to accept both.

```ts
z.string().cidr() // ❌
z.cidrv4() // ✅
z.cidrv6() // ✅
```

## `z.coerce` updates

The input type of all `z.coerce` schemas is now `unknown`.

```ts
const schema = z.coerce.string();
type schemaInput = z.input<typeof schema>;

// Zod 3: string;
// Zod 4: unknown;
```

A missing key on an object schema with a `z.coerce.*` field now errors. Use `.default()` to declare the fallback explicitly.

```ts
const schema = z.object({ foo: z.coerce.boolean() });
schema.parse({});

// Zod 3: { foo: false }
// Zod 4: ZodError: Invalid input: expected nonoptional, received undefined
```

## `.default()` updates

The application of `.default()` has changed in a subtle way. If the input is `undefined`, `ZodDefault` short-circuits the parsing process and returns the default value. The default value must be assignable to the *output type*.

```ts
const schema = z.string()
  .transform(val => val.length)
  .default(0); // should be a number
schema.parse(undefined); // => 0
```

In Zod 3, `.default()` expected a value that matched the *input type*. `ZodDefault` would parse the default value, instead of short-circuiting. As such, the default value must be assignable to the *input type* of the schema.

```ts
// Zod 3
const schema = z.string()
  .transform(val => val.length)
  .default("tuna");
schema.parse(undefined); // => 4
```

To replicate the old behavior, Zod implements a new `.prefault()` API. This is short for "pre-parse default".

```ts
// Zod 3
const schema = z.string()
  .transform(val => val.length)
  .prefault("tuna");
schema.parse(undefined); // => 4
```

## `z.object()`

### defaults applied within optional fields

Defaults inside your properties are applied, even within optional fields. This aligns better with expectations and resolves a long-standing usability issue with Zod 3. This is a subtle change that may cause breakage in code paths that rely on key existence, etc.

```ts
const schema = z.object({
  a: z.string().default("tuna").optional(),
});

schema.parse({});
// Zod 4: { a: "tuna" }
// Zod 3: {}
```

### deprecates `.strict()` and `.passthrough()`

These methods are generally no longer necessary. Instead use the top-level `z.strictObject()` and `z.looseObject()` functions.

```ts
// Zod 3
z.object({ name: z.string() }).strict();
z.object({ name: z.string() }).passthrough();

// Zod 4
z.strictObject({ name: z.string() });
z.looseObject({ name: z.string() });
```

> These methods are still available for backwards compatibility, and they will not be removed. They are considered legacy.

### deprecates `.strip()`

This was never particularly useful, as it was the default behavior of `z.object()`. To convert a strict object to a "regular" one, use `z.object(A.shape)`.

### drops `.nonstrict()`

This long-deprecated alias for `.strip()` has been removed.

### drops `.deepPartial()`

This has been long deprecated in Zod 3 and it now removed in Zod 4. There is no direct alternative to this API. There were lots of footguns in its implementation, and its use is generally an anti-pattern.

### changes `z.unknown()` optionality

The `z.unknown()` and `z.any()` types are no longer marked as "key optional" in the inferred types.

```ts
const mySchema = z.object({
  a: z.any(),
  b: z.unknown()
});
// Zod 3: { a?: any; b?: unknown };
// Zod 4: { a: any; b: unknown };
```

As of `v4.4.0`, the key is required at parse time too. Zod 4.0–4.3 accepted a missing key despite the inferred type above; requiring it is a soundness fix.

```ts
mySchema.parse({}); // ❌ (✅ in v4.3 and earlier)
mySchema.parse({ a: undefined, b: undefined }); // ✅

// use .optional() for a key that may be absent
z.object({ a: z.any().optional() }).parse({}); // ✅
```

### deprecates `.merge()`

The `.merge()` method on `ZodObject` has been deprecated in favor of `.extend()`. The `.extend()` method provides the same functionality, avoids ambiguity around strictness inheritance, and has better TypeScript performance.

```ts
// .merge (deprecated)
const ExtendedSchema = BaseSchema.merge(AdditionalSchema);

// .extend (recommended)
const ExtendedSchema = BaseSchema.extend(AdditionalSchema.shape);

// or use destructuring (best tsc performance)
const ExtendedSchema = z.object({
  ...BaseSchema.shape,
  ...AdditionalSchema.shape,
});
```

> **Note**: For even better TypeScript performance, consider using object destructuring instead of `.extend()`. See the [API documentation](/api?id=extend) for more details.

## `z.nativeEnum()` deprecated

The `z.nativeEnum()` function is now deprecated in favor of just `z.enum()`. The `z.enum()` API has been overloaded to support an enum-like input.

```ts
enum Color {
  Red = "red",
  Green = "green",
  Blue = "blue",
}

const ColorSchema = z.enum(Color); // ✅
```

As part of this refactor of `ZodEnum`, a number of long-deprecated and redundant features have been removed. These were all identical and only existed for historical reasons.

```ts
ColorSchema.enum.Red; // ✅ => "Red" (canonical API)
ColorSchema.Enum.Red; // ❌ removed
ColorSchema.Values.Red; // ❌ removed
```

## `z.array()`

### changes `.nonempty()` type

This now behaves identically to `z.array().min(1)`. The inferred type does not change.

```ts
const NonEmpty = z.array(z.string()).nonempty();

type NonEmpty = z.infer<typeof NonEmpty>; 
// Zod 3: [string, ...string[]]
// Zod 4: string[]
```

The old behavior is now better represented with `z.tuple()` and a "rest" argument. This aligns more closely to TypeScript's type system.

```ts
z.tuple([z.string()], z.string());
// => [string, ...string[]]
```

## `z.promise()` deprecated

There's rarely a reason to use `z.promise()`. If you have an input that may be a `Promise`, just `await` it before parsing it with Zod.

> If you are using `z.promise` to define an async function with `z.function()`, that's no longer necessary either; see the [`ZodFunction`](#function) section below.

## `z.function()`

The result of `z.function()` is no longer a Zod schema. Instead, it acts as a standalone "function factory" for defining Zod-validated functions. The API has also changed; you define an `input` and `output` schema upfront, instead of using `args()` and `.returns()` methods.

<Tabs groupId="lib" items={["Zod 4", "Zod 3"]} persist>
  <Tab value="Zod 4">
    ```ts
    const myFunction = z.function({
      input: [z.object({
        name: z.string(),
        age: z.number().int(),
      })],
      output: z.string(),
    });

    myFunction.implement((input) => {
      return `Hello ${input.name}, you are ${input.age} years old.`;
    });
    ```
  </Tab>

  <Tab value="Zod 3">
    ```ts
    const myFunction = z.function()
      .args(z.object({
        name: z.string(),
        age: z.number().int(),
      }))
      .returns(z.string());

    myFunction.implement((input) => {
      return `Hello ${input.name}, you are ${input.age} years old.`;
    });
    ```
  </Tab>
</Tabs>

If you have a desperate need for a Zod schema with a function type, consider [this workaround](https://github.com/colinhacks/zod/issues/4143#issuecomment-2845134912).

### adds `.implementAsync()`

To define an async function, use `implementAsync()` instead of `implement()`.

```ts
myFunction.implementAsync(async (input) => {
  return `Hello ${input.name}, you are ${input.age} years old.`;
});
```

## `.refine()`

### ignores type predicates

In Zod 3, passing a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) as a refinement functions could still narrow the type of a schema. This wasn't documented but was discussed in some issues. This is no longer the case.

```ts
const mySchema = z.unknown().refine((val): val is string => {
  return typeof val === "string"
});

type MySchema = z.infer<typeof mySchema>; 
// Zod 3: `string`
// Zod 4: still `unknown`
```

### drops `ctx.path`

Zod's new parsing architecture does not eagerly evaluate the `path` array. This was a necessary change that unlocks Zod 4's dramatic performance improvements.

```ts
z.string().superRefine((val, ctx) => {
  ctx.path; // ❌ no longer available
});
```

### drops function as second argument

The following horrifying overload has been removed.

```ts
const longString = z.string().refine(
  (val) => val.length > 10,
  (val) => ({ message: `${val} is not more than 10 characters` })
);
```

{/* ## `.superRefine()` deprecated

  The `.superRefine()` method has been deprecated in favor of `.check()`. The `.check()` method provides the same functionality with a cleaner API. The `.check()` method is also available on Zod and Zod Mini schemas.

  ```ts
  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
    });
  }
  });
  ``` */}

## `z.ostring()`, etc dropped

The undocumented convenience methods `z.ostring()`, `z.onumber()`, etc. have been removed. These were shorthand methods for defining optional string schemas.

## `z.literal()`

### drops `symbol` support

Symbols aren't considered literal values, nor can they be simply compared with `===`. This was an oversight in Zod 3.

## static `.create()` factories dropped

Previously all Zod classes defined a static `.create()` method. These are now implemented as standalone factory functions.

```ts
z.ZodString.create(); // ❌ 
```

## `z.record()`

### drops single argument usage

Before, `z.record()` could be used with a single argument. This is no longer supported.

```ts
// Zod 3
z.record(z.string()); // ✅

// Zod 4
z.record(z.string()); // ❌
z.record(z.string(), z.string()); // ✅
```

### improves enum support

Records have gotten a lot smarter. In Zod 3, passing an enum into `z.record()` as a key schema would result in a partial type

```ts
const myRecord = z.record(z.enum(["a", "b", "c"]), z.number()); 
// { a?: number; b?: number; c?: number; }
```

In Zod 4, this is no longer the case. The inferred type is what you'd expect, and Zod ensures exhaustiveness; that is, it makes sure all enum keys exist in the input during parsing.

```ts
const myRecord = z.record(z.enum(["a", "b", "c"]), z.number());
// { a: number; b: number; c: number; }
```

To replicate the old behavior with optional keys, use `z.partialRecord()`:

```ts
const myRecord = z.partialRecord(z.enum(["a", "b", "c"]), z.number());
// { a?: number; b?: number; c?: number; }
```

## `z.intersection()`

### throws `Error` on merge conflict

Zod intersection parses the input against two schemas, then attempts to merge the results. In Zod 3, when the results were unmergable, Zod threw a `ZodError` with a special `"invalid_intersection_types"` issue.

In Zod 4, this will throw a regular `Error` instead. The existence of unmergable results indicates a structural problem with the schema: an intersection of two incompatible types. Thus, a regular error is more appropriate than a validation error.

## Internal changes

> The typical user of Zod can likely ignore everything below this line. These changes do not impact the user-facing `z` APIs.

There are too many internal changes to list here, but some may be relevant to regular users who are (intentionally or not) relying on certain implementation details. These changes will be of particular interest to library authors building tools on top of Zod.

### updates generics

The generic structure of several classes has changed. Perhaps most significant is the change to the `ZodType` base class:

```ts
// Zod 3
class ZodType<Output, Def extends z.ZodTypeDef, Input = Output> {
  // ...
}

// Zod 4
class ZodType<Output = unknown, Input = unknown> {
  // ...
}
```

The second generic `Def` has been entirely removed. Instead the base class now only tracks `Output` and `Input`. While previously the `Input` value defaulted to `Output`, it now defaults to `unknown`. This allows generic functions involving `z.ZodType` to behave more intuitively in many cases.

```ts
function inferSchema<T extends z.ZodType>(schema: T): T {
  return schema;
};

inferSchema(z.string()); // z.ZodString
```

The need for `z.ZodTypeAny` has been eliminated; just use `z.ZodType` instead.

### adds `z.core`

Many utility functions and types have been moved to the new `zod/v4/core` sub-package, to facilitate code sharing between Zod and Zod Mini.

```ts
import * as z from "zod/v4/core";

function handleError(iss: z.$ZodError) {
  // do stuff
}
```

For convenience, the contents of `zod/v4/core` are also re-exported from `zod` and `zod/mini` under the `z.core` namespace.

```ts
import * as z from "zod";

function handleError(iss: z.core.$ZodError) {
  // do stuff
}
```

Refer to the [Zod Core](/packages/core) docs for more information on the contents of the core sub-library.

### moves `._def`

The `._def` property is now moved to `._zod.def`. The structure of all internal defs is subject to change; this is relevant to library authors but won't be comprehensively documented here.

### drops `ZodEffects`

This doesn't affect the user-facing APIs, but it's an internal change worth highlighting. It's part of a larger restructure of how Zod handles *refinements*.

Previously both refinements and transformations lived inside a wrapper class called `ZodEffects`. That means adding either one to a schema would wrap the original schema in a `ZodEffects` instance. In Zod 4, refinements now live inside the schemas themselves. More accurately, each schema contains an array of "checks"; the concept of a "check" is new in Zod 4 and generalizes the concept of a refinement to include potentially side-effectful transforms like `z.toLowerCase()`.

This is particularly apparent in the Zod Mini API, which heavily relies on the `.check()` method to compose various validations together.

```ts
import * as z from "zod/mini";

z.string().check(
  z.minLength(10),
  z.maxLength(100),
  z.toLowerCase(),
  z.trim(),
);
```

### adds `ZodTransform`

Meanwhile, transforms have been moved into a dedicated `ZodTransform` class. This schema class represents an input transform; in fact, you can actually define standalone transformations now:

```ts
import * as z from "zod";

const schema = z.transform(input => String(input));

schema.parse(12); // => "12"
```

This is primarily used in conjunction with `ZodPipe`. The `.transform()` method now returns an instance of `ZodPipe`.

```ts
z.string().transform(val => val); // ZodPipe<ZodString, ZodTransform>
```

### drops `ZodPreprocess`

As with `.transform()`, the `z.preprocess()` function now returns a `ZodPipe` instance instead of a dedicated `ZodPreprocess` instance.

```ts
z.preprocess(val => val, z.string()); // ZodPipe<ZodTransform, ZodString>
```

### drops `ZodBranded`

Branding is now handled with a direct modification to the inferred type, instead of a dedicated `ZodBranded` class. The user-facing APIs remain the same.

{/* - Dropping support for ES5
  - Zod relies on `Set` internally */}

{/* - `z.keyof` now returns `ZodEnum` instead of `ZodLiteral` */}

{/* ## Changed: `.refine()`

  The `.refine()` method used to accept a function as the second argument. 

  ```ts
  // no longer supported
  const longString = z.string().refine(
  (val) => val.length > 10,
  (val) => ({ message: `${val} is not more than 10 characters` })
  );
  ```

  This can be better represented with the new `error` parameter, so this overload has been removed.

  ```ts
  const longString = z.string().refine((val) => val.length > 10, {
  error: (issue) => `${issue.input} is not more than 10 characters`,
  });
  ``
  */}

{/* 
  - No support for `null` or `undefined` in `z.literal`
  - `z.literal(null)`
  - `z.literal(undefined)`
  - this was never documented */}

{/* - Array min/max/length checks now run after parsing. This means they won't run if the parse has already aborted. */}

{/* - Drops single-argument `z.record()` */}

{/* - Smarter `z.record`: no longer Partial by default */}

{/* - Intersection merge errors are now thrown as Error not ZodError
  - These usually do not reflect a parse error but a structural problem with the schema */}

{/* - Consolidates `unknownKeys` and `catchall` in ZodObject */}

{/* - Dropping
  - `ZodBranded`: purely a static-domain annotation
  - `ZodFunction` */}

{/* - The `description` is now stored in `z.defaultRegistry`, not the def
  - No support for `description` in factory params
  - Descriptions do not cascade in `.optional()`, etc */}

{/* - Enums:
  - ZodEnum and ZodNativeEnum are merged
  - `.Values` and `.Enum` are removed. Use `.enum` instead.
  - `.options` is removed */}


# Release notes

import { Callout } from "fumadocs-ui/components/callout";
import { Tabs, Tab } from "fumadocs-ui/components/tabs";
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';

After a year of active development: Zod 4 is now stable! It's faster, slimmer, more `tsc`-efficient, and implements some long-requested features.

<Callout icon="❤️">
  Huge thanks to [Clerk](https://go.clerk.com/zod-clerk), who supported my work on Zod 4 through their extremely generous [OSS Fellowship](https://clerk.com/blog/zod-fellowship). They were an amazing partner throughout the (much longer than anticipated!) development process.
</Callout>

## Versioning

{/* <Callout> 
  **Update** — `zod@4.0.0` has now been published to npm. To upgrad
  </Callout> */}

{/* To simplify the migration process both for users and Zod's ecosystem of associated libraries, Zod 4 will initially published alongside Zod 3 as part of the `zod@3.25` release. Despite the version number, it is considered stable and ready for production use. */}

To upgrade:

```
npm install zod@^4.0.0
```

{/* Down the road, when there's broad support for Zod 4, we'll publish `zod@4.0.0` on npm. At this point, Zod 4 will be exported from the package root (`"zod"`). The `"zod/v4"` subpath will remain available. For a detailed writeup on the reasons for this versioning scheme, refer to [this issue](https://github.com/colinhacks/zod/issues/4371).  */}

For a complete list of breaking changes, refer to the [Migration guide](/v4/changelog). This post focuses on new features & enhancements.

{/* A number of popular ecosystem packages have Zod 4 support ready or nearly ready. Track the following pull requests for updates:
  - [`drizzle-zod#4478`](https://github.com/drizzle-team/drizzle-orm/pull/4478)
  - [`@hono/zod-validator#1173`](https://github.com/honojs/middleware/pull/1173) */}

## Why a new major version?

Zod v3.0 was released in May 2021 (!). Back then Zod had 2700 stars on GitHub and 600k weekly downloads. Today it has 37.8k stars and 31M weekly downloads (up from 23M when the beta came out 6 weeks ago!). After 24 minor versions, the Zod 3 codebase had hit a ceiling; the most commonly requested features and improvements require breaking changes.

Zod 4 fixes a number of long-standing design limitations of Zod 3 in one fell swoop, paving the way for several long-requested features and a huge leap in performance. It closes 9 of Zod's [10 most upvoted open issues](https://github.com/colinhacks/zod/issues?q=is%3Aissue%20state%3Aopen%20sort%3Areactions-%2B1-desc). With luck, it will serve as the new foundation for many more years to come.

For a scannable breakdown of what's new, see the table of contents. Click on any item to jump to that section.

## Benchmarks

You can run these benchmarks yourself in the Zod repo:

```sh
$ git clone git@github.com:colinhacks/zod.git
$ cd zod
$ git switch v4
$ nub install
```

Then to run a particular benchmark:

```sh
$ nub run bench <name>
```

### 14x faster string parsing

```sh
$ nub run bench string
runtime: node v22.13.0 (arm64-darwin)

benchmark      time (avg)             (min … max)       p75       p99      p999
------------------------------------------------- -----------------------------
• z.string().parse
------------------------------------------------- -----------------------------
zod3          363 µs/iter       (338 µs … 683 µs)    351 µs    467 µs    572 µs
zod4       24'674 ns/iter    (21'083 ns … 235 µs) 24'209 ns 76'125 ns    120 µs

summary for z.string().parse
  zod4
   14.71x faster than zod3
```

### 7x faster array parsing

```sh
$ nub run bench array
runtime: node v22.13.0 (arm64-darwin)

benchmark      time (avg)             (min … max)       p75       p99      p999
------------------------------------------------- -----------------------------
• z.array() parsing
------------------------------------------------- -----------------------------
zod3          147 µs/iter       (137 µs … 767 µs)    140 µs    246 µs    520 µs
zod4       19'817 ns/iter    (18'125 ns … 436 µs) 19'125 ns 44'500 ns    137 µs

summary for z.array() parsing
  zod4
   7.43x faster than zod3
```

### 6.5x faster object parsing

This runs the [Moltar validation library benchmark](https://moltar.github.io/typescript-runtime-type-benchmarks/).

```sh
$ nub run bench object-moltar
benchmark      time (avg)             (min … max)       p75       p99      p999
------------------------------------------------- -----------------------------
• z.object() safeParse
------------------------------------------------- -----------------------------
zod3          805 µs/iter     (771 µs … 2'802 µs)    804 µs    928 µs  2'802 µs
zod4          124 µs/iter     (118 µs … 1'236 µs)    119 µs    231 µs    329 µs

summary for z.object() safeParse
  zod4
   6.5x faster than zod3
```

## 100x reduction in `tsc` instantiations

Consider the following simple file:

```ts
import * as z from "zod";

export const A = z.object({
  a: z.string(),
  b: z.string(),
  c: z.string(),
  d: z.string(),
  e: z.string(),
});

export const B = A.extend({
  f: z.string(),
  g: z.string(),
  h: z.string(),
});
```

Compiling this file with `tsc --extendedDiagnostics` using `"zod/v3"` results in >25000 type instantiations. With `"zod/v4"` it only results in \~175.

<Callout>
  The Zod repo contains a `tsc` benchmarking playground. Try this for yourself using the compiler benchmarks in `packages/tsc`. The exact numbers may change as the implementation evolves.

  ```sh
  $ cd packages/tsc
  $ nub run bench object-with-extend
  ```
</Callout>

More importantly, Zod 4 has redesigned and simplified the generics of `ZodObject` and other schema classes to avoid some pernicious "instantiation explosions". For instance, chaining `.extend()` and `.omit()` repeatedly—something that previously caused compiler issues:

```ts
import * as z from "zod";

export const a = z.object({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});

export const b = a.omit({
  a: true,
  b: true,
  c: true,
});

export const c = b.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});

export const d = c.omit({
  a: true,
  b: true,
  c: true,
});

export const e = d.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});

export const f = e.omit({
  a: true,
  b: true,
  c: true,
});

export const g = f.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});

export const h = g.omit({
  a: true,
  b: true,
  c: true,
});

export const i = h.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});

export const j = i.omit({
  a: true,
  b: true,
  c: true,
});

export const k = j.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});

export const l = k.omit({
  a: true,
  b: true,
  c: true,
});

export const m = l.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});

export const n = m.omit({
  a: true,
  b: true,
  c: true,
});

export const o = n.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});

export const p = o.omit({
  a: true,
  b: true,
  c: true,
});

export const q = p.extend({
  a: z.string(),
  b: z.string(),
  c: z.string(),
});
```

In Zod 3, this took `4000ms` to compile; and adding additional calls to `.extend()` would trigger a "Possibly infinite" error. In Zod 4, this compiles in `400ms`, `10x` faster.

> Coupled with the upcoming [`tsgo`](https://github.com/microsoft/typescript-go) compiler, Zod 4's editor performance will scale to vastly larger schemas and codebases.

## 2x reduction in core bundle size

Consider the following simple script.

```ts
import * as z from "zod";

const schema = z.boolean();

schema.parse(true);
```

It's about as simple as it gets when it comes to validation. That's intentional; it's a good way to measure the *core bundle size*—the code that will end up in the bundle even in simple cases. We'll bundle this with `rollup` using both Zod 3 and Zod 4 and compare the final bundles.

| Package | Bundle (gzip) |
| ------- | ------------- |
| Zod 3   | `12.47kb`     |
| Zod 4   | `5.36kb`      |

The core bundle is \~57% smaller in Zod 4 (2.3x). That's good! But we can do a lot better.

## Introducing Zod Mini

Zod's method-heavy API is fundamentally difficult to tree-shake. Even our simple `z.boolean()` script pulls in the implementations of a bunch of methods we didn't use, like `.optional()`, `.array()`, etc. Writing slimmer implementations can only get you so far. That's where Zod Mini comes in.

```sh
npm install zod@^4.0.0
```

It's a Zod variant with a functional, tree-shakable API that corresponds one-to-one with `zod`. Where Zod uses methods, Zod Mini generally uses wrapper functions:

<Tabs groupId="lib" items={[ "Zod Mini", "Zod"]}>
  <Tab value="Zod Mini">
    ```ts
    import * as z from "zod/mini";

    z.optional(z.string());

    z.union([z.string(), z.number()]);

    z.extend(z.object({ /* ... */ }), { age: z.number() });
    ```
  </Tab>

  <Tab value="Zod">
    ```ts
    import * as z from "zod";

    z.string().optional();

    z.string().or(z.number());

    z.object({ /* ... */ }).extend({ age: z.number() });
    ```
  </Tab>
</Tabs>

Not all methods are gone! The parsing methods are identical in Zod and Zod Mini:

```ts
import * as z from "zod/mini";

z.string().parse("asdf");
z.string().safeParse("asdf");
await z.string().parseAsync("asdf");
await z.string().safeParseAsync("asdf");
```

There's also a general-purpose `.check()` method used to add refinements.

<Tabs groupId="lib" items={[ "Zod Mini", "Zod"]}>
  <Tab value="Zod Mini">
    ```ts
    import * as z from "zod/mini";

    z.array(z.number()).check(
      z.minLength(5), 
      z.maxLength(10),
      z.refine(arr => arr.includes(5))
    );
    ```
  </Tab>

  <Tab value="Zod">
    ```ts
    import * as z from "zod";

    z.array(z.number())
      .min(5)
      .max(10)
      .refine(arr => arr.includes(5));
    ```
  </Tab>
</Tabs>

The following top-level refinements are available in Zod Mini. It should be fairly self-explanatory which Zod methods they correspond to.

```ts
import * as z from "zod/mini";

// custom checks
z.refine();

// first-class checks
z.lt(value);
z.lte(value); // alias: z.maximum()
z.gt(value);
z.gte(value); // alias: z.minimum()
z.positive();
z.negative();
z.nonpositive();
z.nonnegative();
z.multipleOf(value);
z.maxSize(value);
z.minSize(value);
z.size(value);
z.maxLength(value);
z.minLength(value);
z.length(value);
z.regex(regex);
z.lowercase();
z.uppercase();
z.includes(value);
z.startsWith(value);
z.endsWith(value);
z.property(key, schema); // for object schemas; check `input[key]` against `schema`
z.mime(value); // for file schemas (see below)

// overwrites (these *do not* change the inferred type!)
z.overwrite(value => newValue);
z.normalize();
z.trim();
z.toLowerCase();
z.toUpperCase();
```

This more functional API makes it easier for bundlers to tree-shake the APIs you don't use. While regular Zod is still recommended for the majority of use cases, any projects with uncommonly strict bundle size constraints should consider Zod Mini.

### 6.6x reduction in core bundle size

Here's the script from above, updated to use `"zod/mini"` instead of `"zod"`.

```ts
import * as z from "zod/mini";

const schema = z.boolean();
schema.parse(false);
```

When we build this with `rollup`, the gzipped bundle size is `1.88kb`. That's an 85% (6.6x) reduction in core bundle size compared to `zod@3`.

| Package         | Bundle (gzip) |
| --------------- | ------------- |
| Zod 3           | `12.47kb`     |
| Zod 4 (regular) | `5.36kb`      |
| Zod 4 (mini)    | `1.88kb`      |

Learn more on the dedicated [`zod/mini`](/packages/mini) docs page. Complete API details are mixed into existing documentation pages; code blocks contain separate tabs for `"Zod"` and `"Zod Mini"` wherever their APIs diverge.

## Metadata

Zod 4 introduces a new system for adding strongly-typed metadata to your schemas. Metadata isn't stored inside the schema itself; instead it's stored in a "schema registry" that associates a schema with some typed metadata. To create a registry with `z.registry()`:

```ts
import * as z from "zod";

const myRegistry = z.registry<{ title: string; description: string }>();
```

To add schemas to your registry:

```ts
const emailSchema = z.string().email();

myRegistry.add(emailSchema, { title: "Email address", description: "..." });
myRegistry.get(emailSchema);
// => { title: "Email address", ... }
```

Alternatively, you can use the `.register()` method on a schema for convenience:

{/* > Unlike all other Zod methods, `.register()` is *not* immutable, it returns the original schema. */}

```ts
emailSchema.register(myRegistry, { title: "Email address", description: "..." })
// => returns emailSchema
```

### The global registry

Zod also exports a global registry `z.globalRegistry` that accepts some common JSON Schema-compatible metadata:

```ts
z.globalRegistry.add(z.string(), { 
  id: "email_address",
  title: "Email address",
  description: "Provide your email",
  examples: ["naomie@example.com"],
  extraKey: "Additional properties are also allowed"
});
```

### `.meta()`

To conveniently add a schema to `z.globalRegistry`, use the `.meta()` method.

{/* > Unlike `.register()`, `.meta()` *is* immutable; it returns a new instance (a clone of the original schema). */}

```ts
z.string().meta({ 
  id: "email_address",
  title: "Email address",
  description: "Provide your email",
  examples: ["naomie@example.com"],
  // ...
});
```

<Callout>
  For compatibility with Zod 3, `.describe()` is still available, but `.meta()` is preferred.

  ```ts
  z.string().describe("An email address");

  // equivalent to
  z.string().meta({ description: "An email address" });
  ```
</Callout>

## JSON Schema conversion

Zod 4 introduces first-party JSON Schema conversion via `z.toJSONSchema()`.

```ts
import * as z from "zod";

const mySchema = z.object({name: z.string(), points: z.number()});

z.toJSONSchema(mySchema);
// => {
//   type: "object",
//   properties: {
//     name: {type: "string"},
//     points: {type: "number"},
//   },
//   required: ["name", "points"],
// }
```

Any metadata in `z.globalRegistry` is automatically included in the JSON Schema output.

```ts
const mySchema = z.object({
  firstName: z.string().describe("Your first name"),
  lastName: z.string().meta({ title: "last_name" }),
  age: z.number().meta({ examples: [12, 99] }),
});

z.toJSONSchema(mySchema);
// => {
//   type: 'object',
//   properties: {
//     firstName: { type: 'string', description: 'Your first name' },
//     lastName: { type: 'string', title: 'last_name' },
//     age: { type: 'number', examples: [ 12, 99 ] }
//   },
//   required: [ 'firstName', 'lastName', 'age' ]
// }

```

Refer to the [JSON Schema docs](/json-schema) for information on customizing the generated JSON Schema.

## Recursive objects

This was an unexpected one. After years of trying to crack this problem, I finally [found a way](https://x.com/colinhacks/status/1919286275133378670) to properly infer recursive object types in Zod. To define a recursive type:

```ts
const Category = z.object({
  name: z.string(),
  get subcategories(){
    return z.array(Category)
  }
});

type Category = z.infer<typeof Category>;
// { name: string; subcategories: Category[] }
```

You can also represent *mutually recursive types*:

```ts
const User = z.object({
  email: z.email(),
  get posts(){
    return z.array(Post)
  }
});

const Post = z.object({
  title: z.string(),
  get author(){
    return User
  }
});
```

Unlike the Zod 3 pattern for recursive types, there's no type casting required. The resulting schemas are plain `ZodObject` instances and have the full set of methods available.

```ts
Post.pick({ title: true })
Post.partial();
Post.extend({ publishDate: z.date() });
```

## File schemas

To validate `File` instances:

```ts
const fileSchema = z.file();

fileSchema.min(10_000); // minimum .size (bytes)
fileSchema.max(1_000_000); // maximum .size (bytes)
fileSchema.mime(["image/png"]); // MIME type
```

## Internationalization

Zod 4 introduces a new `locales` API for globally translating error messages into different languages.

```ts
import * as z from "zod";
import { en } from "zod/locales";

// configure English locale (default)
z.config(en());
```

See the full list of supported locales in [Customizing errors](/error-customization#locales); this section is always updated with a list of supported languages as they become available.

## Error pretty-printing

The popularity of the [`zod-validation-error`](https://www.npmjs.com/package/zod-validation-error) package demonstrates that there's significant demand for an official API for pretty-printing errors. If you are using that package currently, by all means continue using it.

Zod now implements a top-level `z.prettifyError` function for converting a `ZodError` to a user-friendly formatted string.

```ts
const myError = new z.ZodError([
  {
    code: 'unrecognized_keys',
    keys: [ 'extraField' ],
    path: [],
    message: 'Unrecognized key: "extraField"'
  },
  {
    expected: 'string',
    code: 'invalid_type',
    path: [ 'username' ],
    message: 'Invalid input: expected string, received number'
  },
  {
    origin: 'number',
    code: 'too_small',
    minimum: 0,
    inclusive: true,
    path: [ 'favoriteNumbers', 1 ],
    message: 'Too small: expected number to be >=0'
  }
]);

z.prettifyError(myError);
```

This returns the following pretty-printable multi-line string:

```ts
✖ Unrecognized key: "extraField"
✖ Invalid input: expected string, received number
  → at username
✖ Invalid input: expected number, received string
  → at favoriteNumbers[1]
```

Currently the formatting isn't configurable; this may change in the future.

## Top-level string formats

All "string formats" (email, etc.) have been promoted to top-level functions on the `z` module. This is both more concise and more tree-shakable. The method equivalents (`z.string().email()`, etc.) are still available but have been deprecated. They'll be removed in the next major version.

```ts
z.email();
z.uuidv4();
z.uuidv6();
z.uuidv7();
z.ipv4();
z.ipv6();
z.cidrv4();
z.cidrv6();
z.url();
z.e164();
z.base64();
z.base64url();
z.jwt();
z.lowercase();
z.iso.date();
z.iso.datetime();
z.iso.duration();
z.iso.time();
```

### Custom email regex

The `z.email()` API now supports a custom regular expression. There is no one canonical email regex; different applications may choose to be more or less strict. For convenience Zod exports some common ones.

```ts
// Zod's default email regex (Gmail rules)
// see colinhacks.com/essays/reasonable-email-regex
z.email(); // z.regexes.email

// the regex used by browsers to validate input[type=email] fields
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email
z.email({ pattern: z.regexes.html5Email });

// the classic emailregex.com regex (RFC 5322)
z.email({ pattern: z.regexes.rfc5322Email });

// a loose regex that allows Unicode (good for intl emails)
z.email({ pattern: z.regexes.unicodeEmail });
```

## Template literal types

Zod 4 implements `z.templateLiteral()`. Template literal types are perhaps the biggest feature of TypeScript's type system that wasn't previously representable.

```ts
const hello = z.templateLiteral(["hello, ", z.string()]);
// `hello, ${string}`

const cssUnits = z.enum(["px", "em", "rem", "%"]);
const css = z.templateLiteral([z.number(), cssUnits]);
// `${number}px` | `${number}em` | `${number}rem` | `${number}%`

const email = z.templateLiteral([
  z.string().min(1),
  "@",
  z.string().max(64),
]);
// `${string}@${string}` (the min/max refinements are enforced!)
```

Every Zod schema type that can be stringified stores an internal regex: strings, string formats like `z.email()`, numbers, boolean, bigint, enums, literals, undefined/optional, null/nullable, and other template literals. The `z.templateLiteral` constructor concatenates these into a super-regex, so things like string formats (`z.email()`) are properly enforced (but custom refinements are not!).

Read the [template literal docs](/api#template-literals) for more info.

## Number formats

New numeric "formats" have been added for representing fixed-width integer and float types. These return a `ZodNumber` instance with proper inclusive minimum/maximum constraints already added.

```ts
z.int();      // [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]
z.float32();  // [-3.4028234663852886e38, 3.4028234663852886e38]
z.float64();  // [-1.7976931348623157e308, 1.7976931348623157e308]
z.int32();    // [-2147483648, 2147483647]
z.uint32();   // [0, 4294967295]
```

Similarly the following `bigint` numeric formats have also been added. These integer types exceed what can be safely represented by a `number` in JavaScript, so these return a `ZodBigInt` instance with the proper inclusive minimum/maximum constraints already added.

```ts
z.int64();    // [-9223372036854775808n, 9223372036854775807n]
z.uint64();   // [0n, 18446744073709551615n]
```

## Stringbool

The existing `z.coerce.boolean()` API is very simple: falsy values (`false`, `undefined`, `null`, `0`, `""`, `NaN` etc) become `false`, truthy values become `true`.

This is still a good API, and its behavior aligns with the other `z.coerce` APIs. But some users requested a more sophisticated "env-style" boolean coercion. To support this, Zod 4 introduces `z.stringbool()`:

```ts
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 */); // ZodError<[{ code: "invalid_value" }]>
```

To customize the truthy and falsy values:

```ts
z.stringbool({
  truthy: ["yes", "true"],
  falsy: ["no", "false"]
})
```

Refer to the [`z.stringbool()` docs](/api#stringbool) for more information.

## Simplified error customization

The majority of breaking changes in Zod 4 involve the *error customization* APIs. They were a bit of a mess in Zod 3; Zod 4 makes things significantly more elegant, to the point where I think it's worth highlighting here.

Long story short, there is now a single, unified `error` parameter for customizing errors, replacing the following APIs:

Replace `message` with `error`. (The `message` parameter is still supported but deprecated.)

```diff
- z.string().min(5, { message: "Too short." });
+ z.string().min(5, { error: "Too short." });
```

Replace `invalid_type_error` and `required_error` with `error` (function syntax):

```diff
// Zod 3
- z.string({ 
-   required_error: "This field is required" 
-   invalid_type_error: "Not a string", 
- });

// Zod 4 
+ z.string({ error: (issue) => issue.input === undefined ? 
+  "This field is required" :
+  "Not a string" 
+ });
```

Replace `errorMap` with `error` (function syntax):

```diff
// Zod 3 
- z.string({
-   errorMap: (issue, ctx) => {
-     if (issue.code === "too_small") {
-       return { message: `Value must be >${issue.minimum}` };
-     }
-     return { message: ctx.defaultError };
-   },
- });

// Zod 4
+ z.string({
+   error: (issue) => {
+     if (issue.code === "too_small") {
+       return `Value must be >${issue.minimum}`
+     }
+   },
+ });
```

## Upgraded `z.discriminatedUnion()`

Discriminated unions now support a number of schema types not previously supported, including unions and pipes:

```ts
const MyResult = z.discriminatedUnion("status", [
  // simple literal
  z.object({ status: z.literal("aaa"), data: z.string() }),
  // union discriminator
  z.object({ status: z.union([z.literal("bbb"), z.literal("ccc")]) }),
  // pipe discriminator
  z.object({ status: z.literal("fail").transform(val => val.toUpperCase()) }),
]);
```

Perhaps most importantly, discriminated unions now *compose*—you can use one discriminated union as a member of another.

```ts
const BaseError = z.object({ status: z.literal("failed"), message: z.string() });

const MyResult = z.discriminatedUnion("status", [
  z.object({ status: z.literal("success"), data: z.string() }),
  z.discriminatedUnion("code", [
    BaseError.extend({ code: z.literal(400) }),
    BaseError.extend({ code: z.literal(401) }),
    BaseError.extend({ code: z.literal(500) })
  ])
]);
```

## Multiple values in `z.literal()`

The `z.literal()` API now optionally supports multiple values.

```ts
const httpCodes = z.literal([ 200, 201, 202, 204, 206, 207, 208, 226 ]);

// previously in Zod 3:
const httpCodes = z.union([
  z.literal(200),
  z.literal(201),
  z.literal(202),
  z.literal(204),
  z.literal(206),
  z.literal(207),
  z.literal(208),
  z.literal(226)
]);
```

## Refinements live inside schemas

In Zod 3, they were stored in a `ZodEffects` class that wrapped the original schema. This was inconvenient, as it meant you couldn't interleave `.refine()` with other schema methods like `.min()`.

```ts
z.string()
  .refine(val => val.includes("@"))
  .min(5);
// ^ ❌ Property 'min' does not exist on type ZodEffects<ZodString, string, string>
```

In Zod 4, refinements are stored inside the schemas themselves, so the code above works as expected.

```ts
z.string()
  .refine(val => val.includes("@"))
  .min(5); // ✅
```

### `.overwrite()`

The `.transform()` method is extremely useful, but it has one major downside: the output type is no longer *introspectable* at runtime. The transform function is a black box that can return anything. This means (among other things) there's no sound way to convert the schema to JSON Schema.

```ts
const Squared = z.number().transform(val => val ** 2);
// => ZodPipe<ZodNumber, ZodTransform>
```

Zod 4 introduces a new `.overwrite()` method for representing transforms that *don't change the inferred type*. Unlike `.transform()`, this method returns an instance of the original class. The overwrite function is stored as a refinement, so it doesn't (and can't) modify the inferred type.

```ts
z.number().overwrite(val => val ** 2).max(100);
// => ZodNumber
```

> The existing `.trim()`, `.toLowerCase()` and `.toUpperCase()` methods have been reimplemented using `.overwrite()`.

## An extensible foundation: `zod/v4/core`

While this will not be relevant to the majority of Zod users, it's worth highlighting. The addition of Zod Mini necessitated the creation of a shared sub-package `zod/v4/core` which contains the core functionality shared between Zod and Zod Mini.

I was resistant to this at first, but now I see it as one of Zod 4's most important features. It lets Zod level up from a simple library to a fast validation "substrate" that can be sprinkled into other libraries.

If you're building a schema library, refer to the implementations of Zod and Zod Mini to see how to build on top of the foundation `zod/v4/core` provides. Don't hesitate to get in touch in GitHub discussions or via [X](https://x.com/colinhacks)/[Bluesky](https://bsky.app/profile/colinhacks.com) for help or feedback.

## Wrapping up

I'm planning to write up a series of additional posts explaining the design process behind some major features like Zod Mini. I'll update this section as those get posted.

For library authors, there is now a dedicated [For library authors](/library-authors) guide that describes the best practices for building on top of Zod. It answers common questions about how to support Zod 3 & Zod 4 (including Mini) simultaneously.

```sh
pnpm upgrade zod@latest
```

Happy parsing!<br />
— Colin McDonnell [@colinhacks](https://x.com/colinhacks)


# Versioning

import { Callout } from "fumadocs-ui/components/callout";

### **Update — July 8th, 2025**

`zod@4.0.0` has been published to `npm`. The package root (`"zod"`) now exports Zod 4. All other subpaths have not changed and will remain available forever.

To upgrade to Zod 4:

```
npm install zod@^4.0.0
```

If you are using Zod 4, your existing imports (`"zod/v4"` and `"zod/v4-mini"`) will continue to work forever. However, after upgrading, you can *optionally* rewrite your imports as follows:

|            | Before          | After        |
| ---------- | --------------- | ------------ |
| Zod 4      | `"zod/v4"`      | `"zod"`      |
| Zod 4 Mini | `"zod/v4-mini"` | `"zod/mini"` |
| Zod 3      | `"zod"`         | `"zod/v3"`   |

**Library authors** — if you've already implemented Zod 4 support according to the best practices outlined in the [Library authors](/library-authors) guide, bump your peer dependency to include `zod@^4.0.0`:

```json
// package.json
{
  "peerDependencies": {
    "zod": "^3.25.0 || ^4.0.0"
  }
}
```

*There should be no other code changes necessary.* No code changes were made between the latest `3.25.x` release and `4.0.0`. This does not require a major version bump.

<details>
  <summary><strong>Some notes on subpath versioning</strong></summary>

  Ultimately, the subpath versioning scheme was a necessary evil to force the ecosystem to upgrade in a non-breaking way. If I'd published `zod@4.0.0` out of the gate, most libraries would have naively bumped their peer dependencies, forcing a "version bump avalanche" across the ecosystem.

  As it stands, there is now [broad support](https://x.com/colinhacks/status/1932323805705482339) for Zod 4 across the ecosystem. No migration process is totally painless, but it seems like the "version avalanche" I'd feared didn't happen. By and large, libraries have been able to support Zod 3 and Zod 4 simultaneously: Hono, LangChain, React Hook Form, etc. Several ecosystem maintainers reached out to me specifically to indicate how convenient it was to incrementally add support for Zod 4 (something that would typically require a major version bump). Long story short: this approach worked great! Few other libraries are subject to the same constraints as Zod, but I strongly encourage other libraries with large associated ecosystems to consider a similar approach.
</details>

## Versioning in Zod 4

This is a writeup of Zod 4's approach to versioning, with the goal of making it easier for users and Zod's ecosystem of associated libraries to migrate to Zod 4.

The general approach:

* Zod 4 will not initially be published as `zod@4.0.0` on npm. Instead it will be exported at a subpath (`"zod/v4"`) alongside `zod@3.25.0`
* Despite this, Zod 4 is considered stable and production-ready
* Zod 3 will continue to be exported from the package root (`"zod"`) as well as a new subpath `"zod/v3"`. It will continue to receive bug fixes & stability improvements.

> This approach is analogous to how Golang handles major version changes: [https://go.dev/doc/modules/major-version](https://go.dev/doc/modules/major-version)

Sometime later:

* The package root (`"zod"`) will switch over from exporting Zod 3 to Zod 4
* At this point `zod@4.0.0` will get published to npm
* The `"zod/v4"` subpath will remain available forever

## Why?

Zod occupies a unique place in the ecosystem. Many libraries/frameworks in the ecosystem accept user-defined Zod schemas. This means their user-facing API is strongly coupled to Zod and its various classes/interfaces/utilities. For these libraries/frameworks, a breaking change to Zod necessarily causes a breaking change for their users. A Zod 3 `ZodType` is not assignable to a Zod 4 `ZodType`.

### Why can't libraries just support v3 and v4 simultaneously?

Unfortunately the limitations of peerDependencies (and inconsistencies between package managers) make it extremely difficult to elegantly support two major versions of one library simultaneously.

If I naively published `zod@4.0.0` to npm, the vast majority of the libraries in Zod's ecosystem would need to publish a new major version to properly support Zod 4, include some high-profile libraries like the AI SDK. It would trigger a "version bump avalanche" across the ecosystem and generally create a huge amount of frustration and work.

With subpath versioning, we solve this problem. it provides a straightforward way for libraries to support Zod 3 and Zod 4 (including Zod Mini) simultaneously. They can continue defining a single peerDependency on `"zod"`; no need for more arcane solutions like npm aliases, optional peer dependencies, a `"zod-compat"` package, or other such hacks.

Libraries will need to bump the minimum version of their `"zod"` peer dependency to `zod@^3.25.0`. They can then reference both Zod 3 and Zod 4 in their implementation:

```ts
import * as z3 from "zod/v3"
import * as z4 from "zod/v4"
```

Later, once there's broad support for v4, we'll bump the major version on `npm` and start exporting Zod 4 from the package root, completing the transition. (This has now happened—see the note at the top of this page.)

As long as libraries are importing exclusively from the associated subpaths (not the root), their implementations will continue to work across the major version bump without code changes.

While it may seem unorthodox (at least for people who don't use Go!), this is the only approach I'm aware of that enables a clean, incremental migration path for both Zod's users and the libraries in the broader ecosystem.

***

A deeper dive into why peer dependencies don't work in this situation.

Imagine you're a library trying to build a function `acceptSchema` that accepts a Zod schema. You want to be able to accept Zod 3 or Zod 4 schemas.  In this hypothetical, I'm imagine Zod 4 was published as `zod@4` on npm, no subpaths. Here are your options:

1. Install both zod\@3 and zod\@4 as `dependencies` simultaneously using npm aliases. This works but you end up including your own copies of both Zod 3 and Zod 4. You have no guarantee that your user's Zod schemas are instances of the same z.ZodType class you're pulling from dependencies (`instanceof` checks will probably fail).

2. Use a peer dependency that spans multiple major versions:  `"zod@>=3.0.0"` …but when developing a library you’d still need to pick a version to develop against. Usually you'd install this as a dev dependency. The onus is on you to painstakingly ensure your code works, character-for-character,  across both versions. This is impossible in the case of Zod 3 & Zod 4 because a number of very fundamental classes have simplified/different generics.

3. Optional peer dependencies. i just couldn't find a straight answer about how to reliably determine which peer dep is installed at runtime across all platforms. Many answers online will say "use dynamic imports in a try/catch to check it a package exists". Those folks are assuming you're on the backend because no frontend bundlers have no affordance for this. They'll fail when you try to bundle a dependency that isn't installed. Obviuosly it doesn't matter if you're inside a try/catch during a build step. Also: since we're talking about multiple versions of the same library, you'd need to use npm aliases to differentiate the two versions in your `package.json`. Versions of npm as recent as v10 cannot handle the combination of peer dependencies + npm aliases.

4. `zod-compat`. This extremely hand-wavy solution you see online is "define interfaces for each version that represents some basic functionality". Basically some utility types libraries can use to approximate the real deal. This is error prone, a ton of work, needs to be kept synchronized with the real implementations, and ultimately libraries are developing against a shadow version of your library that probably lacks detail. It also only works for types: if a library depends on any runtime code in Zod it falls apart.

Hence, subpaths.


# Zod Core

import { Callout } from "fumadocs-ui/components/callout"
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';

This sub-package exports the core classes and utilities that are consumed by Zod and Zod Mini. It is not intended to be used directly; instead it's designed to be extended by other packages. It implements:

```ts
import * as z from "zod/v4/core";

// the base class for all Zod schemas
z.$ZodType;

// subclasses of $ZodType that implement common parsers
z.$ZodString
z.$ZodObject
z.$ZodArray
// ...

// the base class for all Zod checks
z.$ZodCheck;

// subclasses of $ZodCheck that implement common checks
z.$ZodCheckMinLength
z.$ZodCheckMaxLength

// the base class for all Zod errors
z.$ZodError;

// issue formats (types only)
{} as z.$ZodIssue;

// utils
z.util.isValidJWT(...);
```

## Schemas

The base class for all Zod schemas is `$ZodType`. It accepts two generic parameters: `Output` and `Input`.

```ts
export class $ZodType<Output = unknown, Input = unknown> {
  _zod: { /* internals */}
}
```

`zod/v4/core` exports a number of subclasses that implement some common parsers. A union of all first-party subclasses is exported as `z.$ZodTypes`.

```ts
export type $ZodTypes =
  | $ZodString
  | $ZodNumber
  | $ZodBigInt
  | $ZodBoolean
  | $ZodDate
  | $ZodSymbol
  | $ZodUndefined
  | $ZodNullable
  | $ZodNull
  | $ZodAny
  | $ZodUnknown
  | $ZodNever
  | $ZodVoid
  | $ZodArray
  | $ZodObject
  | $ZodUnion // $ZodDiscriminatedUnion extends this
  | $ZodIntersection
  | $ZodTuple
  | $ZodRecord
  | $ZodMap
  | $ZodSet
  | $ZodLiteral
  | $ZodEnum
  | $ZodPromise
  | $ZodLazy
  | $ZodOptional
  | $ZodDefault
  | $ZodTemplateLiteral
  | $ZodCustom
  | $ZodTransform
  | $ZodNonOptional
  | $ZodReadonly
  | $ZodNaN
  | $ZodPipe // $ZodCodec and $ZodPreprocess extend this
  | $ZodSuccess
  | $ZodCatch
  | $ZodFile;
```

<Accordions>
  <Accordion title="Inheritance diagram">
    Here is a complete inheritance diagram for the core schema classes:

    ```txt
    - $ZodType
        - $ZodString
            - $ZodStringFormat
                - $ZodGUID
                - $ZodUUID
                - $ZodEmail
                - $ZodURL
                - $ZodEmoji
                - $ZodNanoID
                - $ZodCUID
                - $ZodCUID2
                - $ZodULID
                - $ZodXID
                - $ZodKSUID
                - $ZodISODateTime
                - $ZodISODate
                - $ZodISOTime
                - $ZodISODuration
                - $ZodIPv4
                - $ZodIPv6
                - $ZodCIDRv4
                - $ZodCIDRv6
                - $ZodBase64
                - $ZodBase64URL
                - $ZodE164
                - $ZodJWT
        - $ZodNumber
            - $ZodNumberFormat
        - $ZodBigInt
            - $ZodBigIntFormat
        - $ZodBoolean
        - $ZodSymbol
        - $ZodUndefined
        - $ZodNull
        - $ZodAny
        - $ZodUnknown
        - $ZodNever
        - $ZodVoid
        - $ZodDate
        - $ZodArray
        - $ZodObject
        - $ZodUnion
            - $ZodDiscriminatedUnion
        - $ZodIntersection
        - $ZodTuple
        - $ZodRecord
        - $ZodMap
        - $ZodSet
        - $ZodEnum
        - $ZodLiteral
        - $ZodFile
        - $ZodTransform
        - $ZodOptional
        - $ZodNullable
        - $ZodDefault
        - $ZodPrefault
        - $ZodNonOptional
        - $ZodSuccess
        - $ZodCatch
        - $ZodNaN
        - $ZodPipe
            - $ZodCodec
            - $ZodPreprocess
        - $ZodReadonly
        - $ZodTemplateLiteral
        - $ZodCustom

    ```
  </Accordion>
</Accordions>

## Internals

All `zod/v4/core` subclasses only contain a single property: `_zod`. This property is an object containing the schemas *internals*. The goal is to make `zod/v4/core` as extensible and unopinionated as possible. Other libraries can "build their own Zod" on top of these classes without `zod/v4/core` cluttering up the interface. Refer to the implementations of `zod` and `zod/mini` for examples of how to extend these classes.

The `_zod` internals property contains some notable properties:

* `.def` — The schema's *definition*: this is the object you pass into the class's constructor to create an instance. It completely describes the schema, and it's JSON-serializable.
  * `.def.type` — A string representing the schema's type, e.g. `"string"`, `"object"`, `"array"`, etc.
  * `.def.checks` — An array of *checks* that are executed by the schema after parsing.
* `.input` — A virtual property that "stores" the schema's *inferred input type*.
* `.output` — A virtual property that "stores" the schema's *inferred output type*.
* `.run()` — The schema's internal parser implementation.

If you are implementing a tool (say, a code generator) that must traverse Zod schemas, you can cast any schema to `$ZodTypes` and use the `def` property to discriminate between these classes.

```ts
export function walk(_schema: z.$ZodType) {
  const schema = _schema as z.$ZodTypes;
  const def = schema._zod.def;
  switch (def.type) {
    case "string": {
      // ...
      break;
    }
    case "object": {
      // ...
      break;
    }
  }
}
```

There are a number of subclasses of `$ZodString` that implement various *string formats*. These are exported as `z.$ZodStringFormatTypes`.

```ts
export type $ZodStringFormatTypes =
  | $ZodGUID
  | $ZodUUID
  | $ZodEmail
  | $ZodURL
  | $ZodEmoji
  | $ZodNanoID
  | $ZodCUID
  | $ZodCUID2
  | $ZodULID
  | $ZodXID
  | $ZodKSUID
  | $ZodISODateTime
  | $ZodISODate
  | $ZodISOTime
  | $ZodISODuration
  | $ZodIPv4
  | $ZodIPv6
  | $ZodCIDRv4
  | $ZodCIDRv6
  | $ZodBase64
  | $ZodBase64URL
  | $ZodE164
  | $ZodJWT
```

## Parsing

As the Zod Core schema classes have no methods, there are top-level functions for parsing data.

```ts
import * as z from "zod/v4/core";

const schema = new z.$ZodString({ type: "string" });
z.parse(schema, "hello");
z.safeParse(schema, "hello");
await z.parseAsync(schema, "hello");
await z.safeParseAsync(schema, "hello");
```

## Checks

Every Zod schema contains an array of *checks*. These perform post-parsing refinements (and occasionally mutations) that *do not affect* the inferred type.

```ts
const schema = z.string().check(z.email()).check(z.min(5));
// => $ZodString

schema._zod.def.checks;
// => [$ZodCheckEmail, $ZodCheckMinLength]
```

The base class for all Zod checks is `$ZodCheck`. It accepts a single generic parameter `T`.

```ts
export class $ZodCheck<in T = unknown> {
  _zod: { /* internals */}
}
```

The `_zod` internals property contains some notable properties:

* `.def` — The check's *definition*: this is the object you pass into the class's constructor to create the check. It completely describes the check, and it's JSON-serializable.
  * `.def.check` — A string representing the check's type, e.g. `"min_length"`, `"less_than"`, `"string_format"`, etc.
* `.check()` — Contains the check's validation logic.

`zod/v4/core` exports a number of subclasses that perform some common refinements. All first-party subclasses are exported as a union called `z.$ZodChecks`.

```ts
export type $ZodChecks =
  | $ZodCheckLessThan
  | $ZodCheckGreaterThan
  | $ZodCheckMultipleOf
  | $ZodCheckNumberFormat
  | $ZodCheckBigIntFormat
  | $ZodCheckMaxSize
  | $ZodCheckMinSize
  | $ZodCheckSizeEquals
  | $ZodCheckMaxLength
  | $ZodCheckMinLength
  | $ZodCheckLengthEquals
  | $ZodCheckProperty
  | $ZodCheckMimeType
  | $ZodCheckOverwrite
  | $ZodCheckStringFormat
```

You can use the `._zod.def.check` property to discriminate between these classes.

```ts
const check = {} as z.$ZodChecks;
const def = check._zod.def;

switch (def.check) {
  case "less_than":
  case "greater_than":
    // ...
    break;
}
```

As with schema types, there are a number of subclasses of `$ZodCheckStringFormat` that implement various *string formats*.

```ts
export type $ZodStringFormatChecks =
  | $ZodCheckRegex
  | $ZodCheckLowerCase
  | $ZodCheckUpperCase
  | $ZodCheckIncludes
  | $ZodCheckStartsWith
  | $ZodCheckEndsWith
  | $ZodGUID
  | $ZodUUID
  | $ZodEmail
  | $ZodURL
  | $ZodEmoji
  | $ZodNanoID
  | $ZodCUID
  | $ZodCUID2
  | $ZodULID
  | $ZodXID
  | $ZodKSUID
  | $ZodISODateTime
  | $ZodISODate
  | $ZodISOTime
  | $ZodISODuration
  | $ZodIPv4
  | $ZodIPv6
  | $ZodCIDRv4
  | $ZodCIDRv6
  | $ZodBase64
  | $ZodBase64URL
  | $ZodE164
  | $ZodJWT;
```

Use a nested `switch` to discriminate between the different string format checks.

```ts
const check = {} as z.$ZodChecks;
const def = check._zod.def;

switch (def.check) {
  case "less_than":
  case "greater_than":
  // ...
  case "string_format":
    {
      const formatCheck = check as z.$ZodStringFormatChecks;
      const formatCheckDef = formatCheck._zod.def;

      switch (formatCheckDef.format) {
        case "email":
        case "url":
          // do stuff
      }
    }
    break;
}
```

You'll notice some of these string format *checks* overlap with the string format *types* above. That's because these classes implement both the `$ZodCheck` and `$ZodType` interfaces. That is, they can be used as either a check or a type. In these cases, both `._zod.parse` (the schema parser) and `._zod.check` (the check validation) are executed during parsing. In effect, the instance is prepended to its own `checks` array (though it won't actually exist in `._zod.def.checks`).

```ts
// as a type
z.email().parse("user@example.com");

// as a check
z.string().check(z.email()).parse("user@example.com")
```

## Errors

The base class for all errors in Zod is `$ZodError`.

> For performance reasons, `$ZodError` *does not* extend the built-in `Error` class! So using `instanceof Error` will return `false`.

* The `zod` package implements a subclass of `$ZodError` called `ZodError` with some additional convenience methods.
* The `zod/mini` sub-package directly uses `$ZodError`

```ts
export class $ZodError<T = unknown> implements Error {
 public issues: $ZodIssue[];
}
```

## Issues

The `issues` property corresponds to an array of `$ZodIssue` objects. All issues extend the `z.$ZodIssueBase` interface.

```ts
export interface $ZodIssueBase {
  readonly code?: string;
  readonly input?: unknown;
  readonly path: PropertyKey[];
  readonly message: string;
}
```

Zod defines the following issue subtypes:

```ts
export type $ZodIssue =
  | $ZodIssueInvalidType
  | $ZodIssueTooBig
  | $ZodIssueTooSmall
  | $ZodIssueInvalidStringFormat
  | $ZodIssueNotMultipleOf
  | $ZodIssueUnrecognizedKeys
  | $ZodIssueInvalidUnion
  | $ZodIssueInvalidKey
  | $ZodIssueInvalidElement
  | $ZodIssueInvalidValue
  | $ZodIssueCustom;
```

For details on each type, refer to [the implementation](https://github.com/colinhacks/zod/blob/main/packages/zod/src/v4/core/errors.ts).

{/* ## Best practices

  If you're reading this page, you're likely trying to build some kind of tool or library on top of Zod. This section breaks down some best practices for doing so.

  1. If you're just accept user-defined schemas, use Standard Schema instead

  Zod implements the [Standard Schema](https://standardschema.dev/) specification, a standard interface for schema libraries to expose their validation logic and inferred types to third-party tools. If your goal is to accept user-defined schemas, extracting their inferred types, and using them to parse data, then Standard Schema is all you need. Refer to the Standard Schema website/docs for more information.

  2. Set up `peerDependencies` properly!

  If your tool accepts Zod schemas from a consumer/user, you should add `"zod/v4/core"` to `peerDependencies`. This lets your users "bring their own Zod". Be as flexible as possible with the version range. For example, if your tool is compatible with `zod/v4/core`, you can use the following. This allows your users to bring any version of `zod/v4/core`, avoiding accidental duplicate installs.


  ```json
  {
  "peerDependencies": {
    "zod/v4/core": "*"
  }
  }
  ```

  Since package managers generally won't install your own `peerDependencies`, you'll need to add `zod/v4/core` to your `devDependencies` as well. As new versions of `zod/v4/core` are released, you can update your `devDependencies` to match the latest version. This is important for testing and development purposes.

  ```json
  {
  "peerDependencies": {
    "zod": "*"
  },
  "devDependencies": {
    "zod": "^3.25.0"
  }
  }
  ``` */}


# Zod Mini

import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';

<Callout type="info">
  **Note** — The docs for Zod Mini are interleaved with the regular Zod docs via tabbed code blocks. This page is designed to explain why Zod Mini exists, when to use it, and some key differences from regular Zod.
</Callout>

Zod Mini is a tree-shakable variant of Zod. To try it:

```sh
npm install zod@^4.0.0
```

To import it:

```ts
import * as z from "zod/mini";
```

The same API is published as a standalone package, `@zod/mini`, that re-exports `zod/mini` from a peer dependency on `zod`. Its version tracks `zod`.

```sh
npm install zod @zod/mini
```

```ts
import * as z from "@zod/mini";
```

Zod Mini implements the exact same functionality as `zod`, but using a *functional*, *tree-shakable* API. If you're coming from `zod`, this means you generally will use *functions* in place of methods.

```ts
// regular Zod
const mySchema = z.string().optional().nullable();

// Zod Mini
const mySchema = z.nullable(z.optional(z.string()));
```

## Tree-shaking

Tree-shaking is a technique used by modern bundlers to remove unused code from the final bundle. It's also referred to as *dead-code elimination*.

In regular Zod, schemas provide a range of convenience methods to perform some common operations (e.g. `.min()` on string schemas). Bundlers are generally not able to remove ("treeshake") unused method implementations from your bundle, but they are able to remove unused top-level functions. As such, the API of Zod Mini uses more functions than methods.

```ts
// regular Zod
z.string().min(5).max(10).trim()

// Zod Mini
z.string().check(z.minLength(5), z.maxLength(10), z.trim());
```

To give a general idea about the bundle size reduction, consider this simple script:

```ts
z.boolean().parse(true)
```

Bundling this with Zod and Zod Mini results in the following bundle sizes. Zod Mini results in a 64% reduction.

| Package  | Bundle size (gzip) |
| -------- | ------------------ |
| Zod Mini | `2.12kb`           |
| Zod      | `5.91kb`           |

With a marginally more complex schema that involves object types:

```ts
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`           |

This gives you a sense of the bundle sizes involved. Look closely at these numbers and run your own benchmarks to determine if using Zod Mini is worth it for your use case.

## When (not) to use Zod Mini

In general you should probably use regular Zod unless you have uncommonly strict constraints around bundle size. Many developers massively overestimate the importance of bundle size to application performance. In practice, bundle size on the scale of Zod (`5-10kb` typically) is only a meaningful concern when optimizing front-end bundles for a user base with slow mobile network connections in rural or developing areas.

Let's run through some considerations:

### DX

The API of Zod Mini is more verbose and less discoverable. The methods in Zod's API are much easier to discover & autocomplete through Intellisense than the top-level functions in Zod Mini. It isn't possible to quickly build a schema with chained APIs. (Speaking as the creator of Zod: I spent a lot of time designing the Zod Mini API to be as ergonomic as possible, but I still have a strong preference the standard Zod API.)

### Backend development

If you are using Zod on the backend, bundle size on the scale of Zod is not meaningful. This is true even in resource-constrained environments like Lambda. [This post](https://medium.com/@adtanasa/size-is-almost-all-that-matters-for-optimizing-aws-lambda-cold-starts-cad54f65cbb) benchmarks cold start times with bundles of various sizes. Here is a subset of the results:

| Bundle size                           | Lambda cold start time   |
| ------------------------------------- | ------------------------ |
| `1kb`                                 | `171ms`                  |
| `17kb` (size of gzipped non-Mini Zod) | `171.6ms` (interpolated) |
| `128kb`                               | `176ms`                  |
| `256kb`                               | `182ms`                  |
| `512kb`                               | `279ms`                  |
| `1mb`                                 | `557ms`                  |

The minimum cold start time for a negligible `1kb` bundle is `171ms`. The next bundle size tested is `128kb`, which added only `5ms`. When gzipped, the bundle size for the entirely of regular Zod is roughly `17kb`, which would correspond to a `0.6ms` increase in startup time.

### Internet speed

Generally, the round trip time to the server (`100-200ms`) will dwarf the time required to download an additional `10kb`. Only on slow 3G connections (sub-`1Mbps`) does the download time for an additional `10kb` become more significant. If you aren't optimizing specifically for users in rural or developing areas, your time is likely better spent optimizing something else.

## `ZodMiniType`

All Zod Mini schemas extend the `z.ZodMiniType` base class, which in turn extends `z.core.$ZodType` from [`zod/v4/core`](/packages/core). While this class implements far fewer methods than `ZodType` in `zod`, some particularly useful methods remain.

### `.parse`

This is an obvious one. All Zod Mini schemas implement the same parsing methods as `zod`.

```ts
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()`

In regular Zod there are dedicated methods on schema subclasses for performing common checks:

```ts
import * as z from "zod";

z.string()
  .min(5)
  .max(10)
  .refine(val => val.includes("@"))
  .trim()
```

In Zod Mini such methods aren't implemented. Instead you pass these checks into schemas using the `.check()` method:

```ts
import * as z from "zod/mini"

z.string().check(
  z.minLength(5), 
  z.maxLength(10),
  z.refine(val => val.includes("@")),
  z.trim()
);
```

The following checks are implemented. Some of these checks only apply to schemas of certain types (e.g. strings or numbers). The APIs are all type-safe; TypeScript won't let you add an unsupported check to your schema.

```ts
z.lt(value);
z.lte(value); // alias: z.maximum()
z.gt(value);
z.gte(value); // alias: z.minimum()
z.positive();
z.negative();
z.nonpositive();
z.nonnegative();
z.multipleOf(value);
z.maxSize(value);
z.minSize(value);
z.size(value);
z.maxLength(value);
z.minLength(value);
z.length(value);
z.regex(regex);
z.lowercase();
z.uppercase();
z.includes(value);
z.startsWith(value);
z.endsWith(value);
z.property(key, schema);
z.mime(value);

// custom checks
z.refine()
z.check()   // replaces .superRefine()

// mutations (these do not change the inferred types)
z.overwrite(value => newValue);
z.normalize();
z.trim();
z.toLowerCase();
z.toUpperCase();

// metadata (registers schema in z.globalRegistry)
z.meta({ title: "...", description: "..." });
z.describe("...");
```

### `.register()`

For registering a schema in a [registry](/metadata#registries).

```ts
const myReg = z.registry<{title: string}>();

z.string().register(myReg, { title: "My cool string schema" });
```

### `.brand()`

For *branding* a schema. Refer to the [Branded types](/api#branded-types) docs for more information.

```ts
import * as z from "zod/mini"

const USD = z.string().brand("USD");
```

### `.clone(def)`

Returns an identical clone of the current schema using the provided `def`.

```ts
const mySchema = z.string()

mySchema.clone(mySchema._zod.def);
```

## No default locale

While regular Zod automatically loads the English (`en`) locale, Zod Mini does not. This reduces the bundle size in scenarios where error messages are unnecessary, localized to a non-English language, or otherwise customized.

This means, by default the `message` property of all issues will simply read `"Invalid input"`. To load the English locale:

```ts
import * as z from "zod/mini"
import { en } from "zod/locales";

z.config(en());
```

Refer to the [Locales](/error-customization#internationalization) docs for more on localization.


# Zod

The `zod/v4` package is the "flagship" library of the Zod ecosystem. It strikes a balance between developer experience and bundle size that's ideal for the vast majority of applications.

> If you have uncommonly strict constraints around bundle size, consider [Zod Mini](/packages/mini).

Zod aims to provide a schema API that maps one-to-one to TypeScript's type system.

```ts
import * as z from "zod";

const schema = z.object({
  name: z.string(),
  age: z.number().int().positive(),
  email: z.email(),
});
```

The API relies on methods to provide a concise, chainable, autocomplete-friendly way to define complex types.

```ts
z.string()
  .min(5)
  .max(10)
  .toLowerCase();
```

All schemas extend the `z.ZodType` base class, which in turn extends `z.$ZodType` from [`zod/v4/core`](/packages/core). All instance of `ZodType` implement the following methods:

```ts
import * as z from "zod";

const mySchema = z.string();

// parsing
mySchema.parse(data);
mySchema.safeParse(data);
mySchema.parseAsync(data);
mySchema.safeParseAsync(data);


// refinements
mySchema.refine(refinementFunc);
mySchema.superRefine(refinementFunc); // deprecated, use `.check()`
mySchema.overwrite(overwriteFunc);

// wrappers
mySchema.optional();
mySchema.nonoptional();
mySchema.nullable();
mySchema.nullish();
mySchema.default(defaultValue);
mySchema.array();
mySchema.or(otherSchema);
mySchema.transform(transformFunc);
mySchema.catch(catchValue);
mySchema.pipe(otherSchema);
mySchema.readonly();

// metadata and registries
mySchema.register(registry, metadata);
mySchema.describe(description);
mySchema.meta(metadata);

// utilities
mySchema.check(checkOrFunction);
mySchema.clone(def);
mySchema.brand<T>();
mySchema.isOptional(); // boolean
mySchema.isNullable(); // boolean
```


