diff --git a/packages/docs/content/_index.mdx b/packages/docs/content/_index.mdx new file mode 100644 index 0000000000..f4b7a57cc5 --- /dev/null +++ b/packages/docs/content/_index.mdx @@ -0,0 +1,3106 @@ +--- +title: Defining schemas +--- + +

+ Zod logo +

Zod

+

+ ✨ https://zod.dev ✨ +
+ TypeScript-first schema validation with static type inference +

+

+
+ +

+Zod CI status +Created by Colin McDonnell +License +npm +stars +discord server +

+ +
+ Documentation +   •   + Discord +   •   + npm +   •   + deno +   •   + Issues +   •   + @colinhacks +   •   + tRPC +
+
+ +
+
+ +import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; +import { Pre, CodeBlock } from 'fumadocs-ui/components/codeblock'; + + + + + ```ts + const alskdjf = 'alskdjf'; + ``` + + + ```ts + const alskdjf = 'alskdjf'; + ``` + + + + +> Zod 3.23 is out! View the [release notes](https://github.com/colinhacks/zod/releases/tag/v3.23.0). + +> These docs have been translated into [Chinese](./README_ZH.md). + +## Table of contents + +{/* The full documentation is available both on the [official documentation site](https://zod.js.org/) (recommended) and in `README.md`. + +#### Go to [zod.js.org](https://zod.js.org) >> */} + +- [Table of contents](#table-of-contents) +- [Introduction](#introduction) + - [Sponsors](#sponsors) + - [Gold](#gold) + - [Silver](#silver) + - [Bronze](#bronze) + - [Copper](#copper) + - [Ecosystem](#ecosystem) + - [Resources](#resources) + - [API libraries](#api-libraries) + - [Form integrations](#form-integrations) + - [Zod to X](#zod-to-x) + - [X to Zod](#x-to-zod) + - [Mocking](#mocking) + - [Powered by Zod](#powered-by-zod) + - [Utilities for Zod](#utilities-for-zod) +- [Installation](#installation) + - [Requirements](#requirements) + - [From `npm` (Node/Bun)](#from-npm-nodebun) + - [From `deno.land/x` (Deno)](#from-denolandx-deno) +- [Basic usage](#basic-usage) +- [Primitives](#primitives) +- [Coercion for primitives](#coercion-for-primitives) +- [Literals](#literals) +- [Strings](#strings) + - [Datetimes](#datetimes) + - [Dates](#dates) + - [Times](#times) + - [IP addresses](#ip-addresses) + - [JSON](#json) +- [Numbers](#numbers) +- [BigInts](#bigints) +- [NaNs](#nans) +- [Booleans](#booleans) +- [Dates](#dates) +- [Zod enums](#zod-enums) +- [Native enums](#native-enums) +- [Optionals](#optionals) +- [Nullables](#nullables) +- [Objects](#objects) + - [`.shape`](#shape) + - [`.keyof`](#keyof) + - [`.extend`](#extend) + - [`.merge`](#merge) + - [`.pick/.omit`](#pickomit) + - [`.partial`](#partial) + - [`.deepPartial`](#deeppartial) + - [`.required`](#required) + - [`.passthrough`](#passthrough) + - [`.strict`](#strict) + - [`.strip`](#strip) + - [`.catchall`](#catchall) +- [Arrays](#arrays) + - [`.element`](#element) + - [`.nonempty`](#nonempty) + - [`.min/.max/.length`](#minmaxlength) + - [`.unique`](#unique) +- [Tuples](#tuples) +- [Unions](#unions) +- [Discriminated unions](#discriminated-unions) +- [Records](#records) + - [Record key type](#record-key-type) +- [Maps](#maps) +- [Sets](#sets) +- [Intersections](#intersections) +- [Recursive types](#recursive-types) + - [ZodType with ZodTransform](#zodtype-with-ZodTransform) + - [JSON type](#json-type) + - [Cyclical objects](#cyclical-objects) +- [Promises](#promises) +- [Instanceof](#instanceof) +- [Functions](#functions) +- [Template Literals](#template-literals) +- [Preprocess](#preprocess) +- [Custom schemas](#custom-schemas) +- [Schema methods](#schema-methods) + - [`.parse`](#parse) + - [`.parseAsync`](#parseasync) + - [`.safeParse`](#safeparse) + - [`.safeParseAsync`](#safeparseasync) + - [`.refine`](#refine) + - [Arguments](#arguments) + - [Customize error path](#customize-error-path) + - [Asynchronous refinements](#asynchronous-refinements) + - [Relationship to transforms](#relationship-to-transforms) + - [`.superRefine`](#superrefine) + - [Abort early](#abort-early) + - [Type refinements](#type-refinements) + - [`.transform`](#transform) + - [Chaining order](#chaining-order) + - [Validating during transform](#validating-during-transform) + - [Relationship to refinements](#relationship-to-refinements) + - [Async transforms](#async-transforms) + - [`.default`](#default) + - [`.describe`](#describe) + - [`.catch`](#catch) + - [`.optional`](#optional) + - [`.nullable`](#nullable) + - [`.nullish`](#nullish) + - [`.array`](#array) + - [`.promise`](#promise) + - [`.or`](#or) + - [`.and`](#and) + - [`.brand`](#brand) + - [`.readonly`](#readonly) + - [`.pipe`](#pipe) + - [You can use `.pipe()` to fix common issues with `z.coerce`.](#you-can-use-pipe-to-fix-common-issues-with-zcoerce) +- [Guides and concepts](#guides-and-concepts) + - [Type inference](#type-inference) + - [Writing generic functions](#writing-generic-functions) + - [Constraining allowable inputs](#constraining-allowable-inputs) + - [Error handling](#error-handling) + - [Error formatting](#error-formatting) +- [Comparison](#comparison) + - [Joi](#joi) + - [Yup](#yup) + - [io-ts](#io-ts) + - [Runtypes](#runtypes) + - [Ow](#ow) +- [Changelog](#changelog) + +## Introduction + +Zod is a TypeScript-first schema declaration and validation library. I'm using the term "schema" to broadly refer to any data type, from a simple `string` to a complex nested object. + +Zod is designed to be as developer-friendly as possible. The goal is to eliminate duplicative type declarations. With Zod, you declare a validator _once_ and Zod will automatically infer the static TypeScript type. It's easy to compose simpler types into complex data structures. + +Some other great aspects: + +- Zero dependencies +- Works in Node.js and all modern browsers +- Tiny: 8kb minified + zipped +- Immutable: methods (e.g. `.optional()`) return a new instance +- Concise, chainable interface +- Functional approach: [parse, don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) +- Works with plain JavaScript too! You don't need to use TypeScript. + +### Sponsors + +Sponsorship at any level is appreciated and encouraged. For individual developers, consider the [Cup of Coffee tier](https://github.com/sponsors/colinhacks). If you built a paid product using Zod, consider one of the [podium tiers](https://github.com/sponsors/colinhacks). + +#### Gold + +> This tier was just added. [Be the first Gold Sponsor!](https://github.com/sponsors/colinhacks/sponsorships?sponsor=colinhacks&tier_id=399648&preview=false) + +{/* + + + +
+ + XXX + +
+ XXX +
+ example.com +
*/} + +#### Silver + + + + + + + + + + + + + + + + + + + + +
+ Cerbos +
+ Cerbos +
+ Scalar.com logo +
+ Scalar +
+ Speakeasy API +
+ Speakeasy +
+ Deletype logo +
+ Deletype +
+ Trigger.dev logo +
+ Trigger.dev +
+ Transloadit logo +
+ Transloadit +
+ Infisical logo +
+ Infisical +
+ Whop logo +
+ Whop +
+ CryptoJobsList logo +
+ CryptoJobsList +
+ Plain logo +
+ Plain. +
+ Inngest logo +
+ Inngest +
+ Storyblok CMS +
+ Storyblok +
+ Mux logo +
+ Mux +
+ @emreb +
+ @emreb +
+ +#### Bronze + + + + + + + + + + + +
NumericMarcato PartnersIntervalSeasoned Software
Bamboo Creative
+ +#### Copper + + + + + + + + + + + + + + + + + + + + + + + + + + +
Brandon BayerJiří BrabecAlex JohanssonFungible Systems
AdaptableAvana WalletJason LengstorfGlobal Illumination, Inc.
MasterBornRyan PalmerMichael SweeneyNextbase
RemotionConnor SinnottMohammad-Ali A'râbiSupatool
+ +### 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, tell me about it [on Twitter](https://twitter.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://twitter.com/mattpocockuk) +- [Fixing TypeScript's Blindspot: Runtime Typechecking](https://www.youtube.com/watch?v=rY_XqfSHock) by [@jherr](https://twitter.com/jherr) + +#### API libraries + +- [`tRPC`](https://github.com/trpc/trpc): Build end-to-end typesafe APIs without GraphQL. +- [`@anatine/zod-nestjs`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-nestjs): Helper methods for using Zod in a NestJS project. +- [`zod-endpoints`](https://github.com/flock-community/zod-endpoints): Contract-first strictly typed endpoints with Zod. OpenAPI compatible. +- [`zhttp`](https://github.com/evertdespiegeleer/zhttp): An OpenAPI compatible, strictly typed http library with Zod input and response validation. +- [`domain-functions`](https://github.com/SeasonedSoftware/domain-functions/): Decouple your business logic from your framework using composable functions. With first-class type inference from end to end powered by Zod schemas. +- [`@zodios/core`](https://github.com/ecyrbe/zodios): A typescript API client with runtime and compile time validation backed by axios and zod. +- [`express-zod-api`](https://github.com/RobinTail/express-zod-api): Build Express-based APIs with I/O schema validation and custom middlewares. +- [`tapiduck`](https://github.com/sumukhbarve/monoduck/blob/main/src/tapiduck/README.md): End-to-end typesafe JSON APIs with Zod and Express; a bit like tRPC, but simpler. +- [`koa-zod-router`](https://github.com/JakeFenley/koa-zod-router): Create typesafe routes in Koa with I/O validation using Zod. + +#### Form integrations + +- [`react-hook-form`](https://github.com/react-hook-form/resolvers#zod): A first-party Zod resolver for React Hook Form. +- [`zod-validation-error`](https://github.com/causaly/zod-validation-error): Generate user-friendly error messages from `ZodError`s. +- [`zod-formik-adapter`](https://github.com/robertLichtnow/zod-formik-adapter): A community-maintained Formik adapter for Zod. +- [`react-zorm`](https://github.com/esamattis/react-zorm): Standalone `
` generation and validation for React using Zod. +- [`zodix`](https://github.com/rileytomasek/zodix): Zod utilities for FormData and URLSearchParams in Remix loaders and actions. +- [`conform`](https://conform.guide/api/zod/parseWithZod): A typesafe form validation library for progressive enhancement of HTML forms. Works with Remix and Next.js. +- [`remix-params-helper`](https://github.com/kiliman/remix-params-helper): Simplify integration of Zod with standard URLSearchParams and FormData for Remix apps. +- [`formik-validator-zod`](https://github.com/glazy/formik-validator-zod): Formik-compliant validator library that simplifies using Zod with Formik. +- [`zod-i18n-map`](https://github.com/aiji42/zod-i18n): Useful for translating Zod error messages. +- [`@modular-forms/solid`](https://github.com/fabian-hiller/modular-forms): Modular form library for SolidJS that supports Zod for validation. +- [`houseform`](https://github.com/crutchcorn/houseform/): A React form library that uses Zod for validation. +- [`sveltekit-superforms`](https://github.com/ciscoheat/sveltekit-superforms): Supercharged form library for SvelteKit with Zod validation. +- [`mobx-zod-form`](https://github.com/MonoidDev/mobx-zod-form): Data-first form builder based on MobX & Zod. +- [`@vee-validate/zod`](https://github.com/logaretm/vee-validate/tree/main/packages/zod): Form library for Vue.js with Zod schema validation. + +#### Zod to X + +- [`zod-to-ts`](https://github.com/sachinraja/zod-to-ts): Generate TypeScript definitions from Zod schemas. +- [`zod-to-json-schema`](https://github.com/StefanTerdell/zod-to-json-schema): Convert your Zod schemas into [JSON Schemas](https://json-schema.org/). +- [`@anatine/zod-openapi`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-openapi): Converts a Zod schema to an OpenAPI v3.x `SchemaObject`. +- [`zod-fast-check`](https://github.com/DavidTimms/zod-fast-check): Generate `fast-check` arbitraries from Zod schemas. +- [`zod-dto`](https://github.com/kbkk/abitia/tree/master/packages/zod-dto): Generate Nest.js DTOs from a Zod schema. +- [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod): Create Fastify type providers from Zod schemas. +- [`zod-to-openapi`](https://github.com/asteasolutions/zod-to-openapi): Generate full OpenAPI (Swagger) docs from Zod, including schemas, endpoints & parameters. +- [`nestjs-graphql-zod`](https://github.com/incetarik/nestjs-graphql-zod): Generates NestJS GraphQL model classes from Zod schemas. Provides GraphQL method decorators working with Zod schemas. +- [`zod-openapi`](https://github.com/samchungy/zod-openapi): Create full OpenAPI v3.x documentation from Zod schemas. +- [`fastify-zod-openapi`](https://github.com/samchungy/fastify-zod-openapi): Fastify type provider, validation, serialization and @fastify/swagger support for Zod schemas. +- [`typeschema`](https://typeschema.com/): Universal adapter for schema validation. + +#### X to Zod + +- [`ts-to-zod`](https://github.com/fabien0102/ts-to-zod): Convert TypeScript definitions into Zod schemas. +- [`@runtyping/zod`](https://github.com/johngeorgewright/runtyping/tree/master/packages/zod): Generate Zod from static types & JSON schema. +- [`json-schema-to-zod`](https://github.com/StefanTerdell/json-schema-to-zod): Convert your [JSON Schemas](https://json-schema.org/) into Zod schemas. [Live demo](https://StefanTerdell.github.io/json-schema-to-zod-react/). +- [`json-to-zod`](https://github.com/rsinohara/json-to-zod): Convert JSON objects into Zod schemas. [Live demo](https://rsinohara.github.io/json-to-zod-react/). +- [`graphql-codegen-typescript-validation-schema`](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema): GraphQL Code Generator plugin to generate form validation schema from your GraphQL schema. +- [`zod-prisma`](https://github.com/CarterGrimmeisen/zod-prisma): Generate Zod schemas from your Prisma schema. +- [`Supervillain`](https://github.com/Southclaws/supervillain): Generate Zod schemas from your Go structs. +- [`prisma-zod-generator`](https://github.com/omar-dulaimi/prisma-zod-generator): Emit Zod schemas from your Prisma schema. +- [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod. +- [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models. +- [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas. +- [`@sanity-typed/zod`](https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/zod): Generate Zod Schemas from [Sanity Schemas](https://www.sanity.io/docs/schema-types). +- [`java-to-zod`](https://github.com/ivangreene/java-to-zod): Convert POJOs to Zod schemas +- [`Orval`](https://github.com/anymaniax/orval): Generate Zod schemas from OpenAPI schemas + +#### Mocking + +- [`@anatine/zod-mock`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock): Generate mock data from a Zod schema. Powered by [faker.js](https://github.com/faker-js/faker). +- [`zod-mocking`](https://github.com/dipasqualew/zod-mocking): Generate mock data from your Zod schemas. +- [`zod-fixture`](https://github.com/timdeschryver/zod-fixture): Use your zod schemas to automate the generation of non-relevant test fixtures in a deterministic way. +- [`zocker`](https://zocker.sigrist.dev): Generate plausible mock-data from your schemas. +- [`zodock`](https://github.com/ItMaga/zodock) Generate mock data based on Zod schemas. + +#### Powered by Zod + +- [`freerstore`](https://github.com/JacobWeisenburger/freerstore): Firestore cost optimizer. +- [`slonik`](https://github.com/gajus/slonik/tree/gajus/add-zod-validation-backwards-compatible#runtime-validation-and-static-type-inference): Node.js Postgres client with strong Zod integration. +- [`soly`](https://github.com/mdbetancourt/soly): Create CLI applications with zod. +- [`pastel`](https://github.com/vadimdemedes/pastel): Create CLI applications with react, zod, and ink. +- [`zod-xlsx`](https://github.com/sidwebworks/zod-xlsx): A xlsx based resource validator using Zod schemas. +- [`znv`](https://github.com/lostfictions/znv): Type-safe environment parsing and validation for Node.js with Zod schemas. +- [`zod-config`](https://github.com/alexmarqs/zod-config): Load configurations across multiple sources with flexible adapters, ensuring type safety with Zod. + +#### Utilities for Zod + +- [`zod_utilz`](https://github.com/JacobWeisenburger/zod_utilz): Framework agnostic utilities for Zod. +- [`zod-playground`](https://github.com/marilari88/zod-playground): A tool for learning and testing Zod schema validation functionalities. [Link](https://zod-playground.vercel.app/). +- [`zod-sandbox`](https://github.com/nereumelo/zod-sandbox): Controlled environment for testing zod schemas. [Live demo](https://zod-sandbox.vercel.app/). +- [`zod-dev`](https://github.com/schalkventer/zod-dev): Conditionally disables Zod runtime parsing in production. +- [`zod-accelerator`](https://github.com/duplojs/duplojs-zod-accelerator): Accelerates Zod's throughput up to ~100x. + +## Installation + +### Requirements + +- TypeScript 4.5+! +- You must enable `strict` mode in your `tsconfig.json`. This is a best practice for all TypeScript projects. + + ```ts + // tsconfig.json + { + // ... + "compilerOptions": { + // ... + "strict": true + } + } + ``` + +### From `npm` (Node/Bun) + +```sh +npm install zod # npm +yarn add zod # yarn +bun add zod # bun +pnpm add zod # pnpm +``` + +Zod also publishes a canary version on every commit. To install the canary: + +```sh +npm install zod@canary # npm +yarn add zod@canary # yarn +bun add zod@canary # bun +pnpm add zod@canary # pnpm +``` + +### From `deno.land/x` (Deno) + +Unlike Node, Deno relies on direct URL imports instead of a package manager like NPM. Zod is available on [deno.land/x](https://deno.land/x). The latest version can be imported like so: + +```ts +import { z } from "https://deno.land/x/zod/mod.ts"; +``` + +You can also specify a particular version: + +```ts +import { z } from "https://deno.land/x/zod@v3.16.1/mod.ts"; +``` + +> The rest of this README assumes you are using npm and importing directly from the `"zod"` package. + +## Basic usage + +Creating a simple string schema + +```ts +import { z } from "zod"; + +// creating a schema for strings +const mySchema = z.string(); + +// parsing +mySchema.parse("tuna"); // => "tuna" +mySchema.parse(12); // => throws ZodError + +// "safe" parsing (doesn't throw error if validation fails) +mySchema.safeParse("tuna"); // => { success: true; data: "tuna" } +mySchema.safeParse(12); // => { success: false; error: ZodError } +``` + +Creating an object schema + +```ts +import { z } from "zod"; + +const User = z.object({ + username: z.string(), +}); + +User.parse({ username: "Ludwig" }); + +// extract the inferred type +type User = z.infer; +// { username: string } +``` + +## Primitives + +```ts +import { z } from "zod"; + +// primitive values +z.string(); +z.number(); +z.bigint(); +z.boolean(); +z.date(); +z.symbol(); + +// empty types +z.undefined(); +z.null(); +z.void(); // accepts undefined + +// catch-all types +// allows any value +z.any(); +z.unknown(); + +// never type +// allows no values +z.never(); +``` + +## Coercion for primitives + +Zod now provides a more convenient way to coerce primitive values. + +```ts +const schema = z.coerce.string(); +schema.parse("tuna"); // => "tuna" +schema.parse(12); // => "12" +``` + +During the parsing step, the input is passed through the `String()` function, which is a JavaScript built-in for coercing data into strings. + +```ts +schema.parse(12); // => "12" +schema.parse(true); // => "true" +schema.parse(undefined); // => "undefined" +schema.parse(null); // => "null" +``` + +The returned schema is a normal `ZodString` instance so you can use all string methods. + +```ts +z.coerce.string().email().min(5); +``` + +**How coercion works** + +All primitive types support coercion. Zod coerces all inputs using the built-in constructors: `String(input)`, `Number(input)`, `new Date(input)`, etc. + +```ts +z.coerce.string(); // String(input) +z.coerce.number(); // Number(input) +z.coerce.boolean(); // Boolean(input) +z.coerce.bigint(); // BigInt(input) +z.coerce.date(); // new Date(input) +``` + +**Note** — 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 more control over coercion logic, consider using [`z.preprocess`](#preprocess) or [`z.pipe()`](#pipe). + +## 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); // bigint literal +const tru = z.literal(true); + +const terrificSymbol = Symbol("terrific"); +const terrific = z.literal(terrificSymbol); + +// retrieve literal value +tuna.value; // "tuna" +``` + +> Currently there is no support for Date literals in Zod. If you have a use case for this feature, please file an issue. + +## Strings + +Zod includes a handful of string-specific validations. + +```ts +// validations +z.string().max(5); +z.string().min(5); +z.string().length(5); +z.string().email(); +z.string().url(); +z.string().emoji(); +z.string().jwt(); // validates format, NOT signature +z.string().jwt({ alg: "HS256" }); // specify algorithm +z.string().uuid(); +z.string().nanoid(); +z.string().cuid(); +z.string().cuid2(); +z.string().ulid(); +z.string().xid(); +z.string().ksuid(); +z.string().regex(regex); +z.string().includes(string); +z.string().startsWith(string); +z.string().endsWith(string); +z.string().datetime(); // ISO 8601; by default only `Z` timezone allowed +z.string().ip(); // defaults to allow both IPv4 and IPv6 + +// transforms +z.string().trim(); // trim whitespace +z.string().toLowerCase(); // toLowerCase +z.string().toUpperCase(); // toUpperCase + +// added in Zod 3.23 +z.string().date(); // ISO date format (YYYY-MM-DD) +z.string().time(); // ISO time format (HH:mm:ss[.SSSSSS]) +z.string().duration(); // ISO 8601 duration +z.string().base64(); +z.string().e164(); // E.164 number format +``` + +> Check out [validator.js](https://github.com/validatorjs/validator.js) for a bunch of other useful string validation functions that can be used in conjunction with [Refinements](#refine). + +You can customize some common error messages when creating a string schema. + +```ts +const name = z.string({ + required_error: "Name is required", + invalid_type_error: "Name must be a string", +}); +``` + +When using validation methods, you can pass in an additional argument to provide a custom error message. + +```ts +z.string().min(5, { message: "Must be 5 or more characters long" }); +z.string().max(5, { message: "Must be 5 or fewer characters long" }); +z.string().length(5, { message: "Must be exactly 5 characters long" }); +z.string().email({ message: "Invalid email address" }); +z.string().url({ message: "Invalid url" }); +z.string().emoji({ message: "Contains non-emoji characters" }); +z.string().uuid({ message: "Invalid UUID" }); +z.string().includes("tuna", { message: "Must include tuna" }); +z.string().startsWith("https://", { message: "Must provide secure URL" }); +z.string().endsWith(".com", { message: "Only .com domains allowed" }); +z.string().datetime({ message: "Invalid datetime string! Must be UTC." }); +z.string().date({ message: "Invalid date string!" }); +z.string().time({ message: "Invalid time string!" }); +z.string().ip({ message: "Invalid IP address" }); +``` + +### 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.string().datetime()` method enforces ISO 8601; default is no timezone offsets and arbitrary sub-second decimal precision. + +```ts +const datetime = z.string().datetime(); + +datetime.parse("2020-01-01T00:00:00Z"); // pass +datetime.parse("2020-01-01T00:00:00.123Z"); // pass +datetime.parse("2020-01-01T00:00:00.123456Z"); // pass (arbitrary precision) +datetime.parse("2020-01-01T00:00:00+02:00"); // fail (no offsets allowed) +``` + +Timezone offsets can be allowed by setting the `offset` option to `true`. + +```ts +const datetime = z.string().datetime({ offset: true }); + +datetime.parse("2020-01-01T00:00:00+02:00"); // pass +datetime.parse("2020-01-01T00:00:00.123+02:00"); // pass (millis optional) +datetime.parse("2020-01-01T00:00:00.123+0200"); // pass (millis optional) +datetime.parse("2020-01-01T00:00:00.123+02"); // pass (only offset hours) +datetime.parse("2020-01-01T00:00:00Z"); // pass (Z still supported) +``` + +You can additionally constrain the allowable `precision`. By default, arbitrary sub-second precision is supported (but optional). + +```ts +const datetime = z.string().datetime({ precision: 3 }); + +datetime.parse("2020-01-01T00:00:00.123Z"); // pass +datetime.parse("2020-01-01T00:00:00Z"); // fail +datetime.parse("2020-01-01T00:00:00.123456Z"); // fail +``` + +### Dates + +> Added in Zod 3.23 + +The `z.string().date()` method validates strings in the format `YYYY-MM-DD`. + +```ts +const date = z.string().date(); + +date.parse("2020-01-01"); // pass +date.parse("2020-1-1"); // fail +date.parse("2020-01-32"); // fail +``` + +### Times + +> Added in Zod 3.23 + +The `z.string().time()` method validates strings in the format `HH:MM:SS[.s+]`. The second can include arbitrary decimal precision. It does not allow timezone offsets of any kind. + +```ts +const time = z.string().time(); + +time.parse("00:00:00"); // pass +time.parse("09:52:31"); // pass +time.parse("23:59:59.9999999"); // pass (arbitrary precision) + +time.parse("00:00:00.123Z"); // fail (no `Z` allowed) +time.parse("00:00:00.123+02:00"); // fail (no offsets allowed) +``` + +You can set the `precision` option to constrain the allowable decimal precision. + +```ts +const time = z.string().time({ precision: 3 }); + +time.parse("00:00:00.123"); // pass +time.parse("00:00:00.123456"); // fail +time.parse("00:00:00"); // fail +``` + +### IP addresses + +The `z.string().ip()` method by default validate IPv4 and IP 6. + +```ts +const ip = z.string().ip(); + +ip.parse("192.168.1.1"); // pass +ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // pass +ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:192.168.1.1"); // pass + +ip.parse("256.1.1.1"); // fail +ip.parse("84d5:51a0:9114:gggg:4cfa:f2d7:1f12:7003"); // fail +``` + +You can additionally set the IP `version`. + +```ts +const ipv4 = z.string().ip({ version: "v4" }); +ipv4.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // fail + +const ipv6 = z.string().ip({ version: "v6" }); +ipv6.parse("192.168.1.1"); // fail +``` + +### JSON + +The `z.string().json(...)` method parses strings as JSON, then [pipes](#pipe) the result to another specified schema. + +```ts +const Env = z.object({ + API_CONFIG: z.string().json( + z.object({ + host: z.string(), + port: z.number().min(1000).max(2000), + }) + ), + SOME_OTHER_VALUE: z.string(), +}); + +const env = Env.parse({ + API_CONFIG: '{ "host": "example.com", "port": 1234 }', + SOME_OTHER_VALUE: "abc123", +}); + +env.API_CONFIG.host; // returns parsed value +``` + +If invalid JSON is encountered, the syntax error will be wrapped and put into a parse error: + +```ts +const env = Env.safeParse({ + API_CONFIG: "not valid json!", + SOME_OTHER_VALUE: "abc123", +}); + +if (!env.success) { + console.log(env.error); // ... Unexpected token n in JSON at position 0 ... +} +``` + +This is recommended over using `z.string().transform(s => JSON.parse(s))`, since that will not catch parse errors, even when using `.safeParse`. + +### E.164 telephone numbers + +The z.string().e164() method can be used to validate international telephone numbers. + +```ts +const e164Number = z.string().e164(); + +e164Number.parse("+1555555"); // pass +e164Number.parse("+155555555555555"); // pass + +e164Number.parse("555555555"); // fail +e164Number.parse("+1 1555555"); // fail +``` + +## Numbers + +You can customize certain error messages when creating a number schema. + +```ts +const age = z.number({ + required_error: "Age is required", + invalid_type_error: "Age must be a number", +}); +``` + +Zod includes a handful of number-specific validations. + +```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().int(); // value must be an integer + +z.number().positive(); // > 0 +z.number().nonnegative(); // >= 0 +z.number().negative(); // < 0 +z.number().nonpositive(); // <= 0 + +z.number().multipleOf(5); // Evenly divisible by 5. Alias .step(5) + +z.number().finite(); // value must be finite, not Infinity or -Infinity +z.number().safe(); // value must be between Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER +``` + +Optionally, you can pass in a second argument to provide a custom error message. + +```ts +z.number().lte(5, { message: "this👏is👏too👏big" }); +``` + +## BigInts + +Zod includes a handful of bigint-specific validations. + +```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(); // > 0n +z.bigint().nonnegative(); // >= 0n +z.bigint().negative(); // < 0n +z.bigint().nonpositive(); // <= 0n + +z.bigint().multipleOf(5n); // Evenly divisible by 5n. +``` + +## NaNs + +You can customize certain error messages when creating a nan schema. + +```ts +const isNaN = z.nan({ + required_error: "isNaN is required", + invalid_type_error: "isNaN must be 'not a number'", +}); +``` + +## Booleans + +You can customize certain error messages when creating a boolean schema. + +```ts +const isActive = z.boolean({ + required_error: "isActive is required", + invalid_type_error: "isActive must be a boolean", +}); +``` + +## Dates + +Use z.date() to validate `Date` instances. + +```ts +z.date().safeParse(new Date()); // success: true +z.date().safeParse("2022-01-12T00:00:00.000Z"); // success: false +``` + +You can customize certain error messages when creating a date schema. + +```ts +const myDateSchema = z.date({ + required_error: "Please select a date and time", + invalid_type_error: "That's not a date!", +}); +``` + +Zod provides a handful of date-specific validations. + +```ts +z.date().min(new Date("1900-01-01"), { message: "Too old" }); +z.date().max(new Date(), { message: "Too young!" }); +``` + +**Coercion to Date** + +Since [zod 3.20](https://github.com/colinhacks/zod/releases/tag/v3.20), use [`z.coerce.date()`](#coercion-for-primitives) to pass the input through `new Date(input)`. + +```ts +const dateSchema = z.coerce.date(); +type DateSchema = z.infer; +// type DateSchema = Date + +/* valid dates */ +console.log(dateSchema.safeParse("2023-01-10T00:00:00.000Z").success); // true +console.log(dateSchema.safeParse("2023-01-10").success); // true +console.log(dateSchema.safeParse("1/10/23").success); // true +console.log(dateSchema.safeParse(new Date("1/10/23")).success); // true + +/* invalid dates */ +console.log(dateSchema.safeParse("2023-13-10").success); // false +console.log(dateSchema.safeParse("0000-00-00").success); // false +``` + +For older zod versions, use [`z.preprocess`](#preprocess) like [described in this thread](https://github.com/colinhacks/zod/discussions/879#discussioncomment-2036276). + +## Files (Browser only) + +Use z.file() to validate `File` instances. + +```ts +z.file().safeParse(new File(["foobar"], "foobar.txt", { type: "text/plain" })); // success: true +z.file().safeParse("foobar"); // success: false +``` + +You can customize certain error messages when creating a file schema. + +```ts +const myFileSchema = z.file({ + required_error: "Please select a file", + invalid_type_error: "That's not a file!", +}); +``` + +Zod provides a handful of file-specific validations. + +```ts +z.file().min(100, { message: "Too small" }); +z.file().max(10_000, { message: "Too large!" }); + +z.file().accept([".txt", ".csv"], { + message: "Accepted file types: .txt, .csv", +}); +z.file().accept(["text/plain"], { + message: "Accepted file type: text/plain", +}); + +z.file().filename(z.string().min(3), { + message: "Filename must be at least 3 characters long", +}); +``` + +## Zod enums + +```ts +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); +type FishEnum = z.infer; +// 'Salmon' | 'Tuna' | 'Trout' +``` + +`z.enum` is a Zod-native way to declare a schema with a fixed set of allowable _string_ values. Pass the array of values directly into `z.enum()`. Alternatively, use `as const` to define your enum values as a tuple of strings. See the [const assertion docs](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) for details. + +```ts +const VALUES = ["Salmon", "Tuna", "Trout"] as const; +const FishEnum = z.enum(VALUES); +``` + +This is not allowed, since Zod isn't able to infer the exact values of each element. + +```ts +const fish = ["Salmon", "Tuna", "Trout"]; +const FishEnum = z.enum(fish); +``` + +**`.enum`** + +To get autocompletion with a Zod enum, use the `.enum` property of your schema: + +```ts +FishEnum.enum.Salmon; // => autocompletes + +FishEnum.enum; +/* +=> { + Salmon: "Salmon", + Tuna: "Tuna", + Trout: "Trout", +} +*/ +``` + +You can also retrieve the list of options as a tuple with the `.options` property: + +```ts +FishEnum.options; // ["Salmon", "Tuna", "Trout"]; +``` + +**`.exclude/.extract()`** + +You can create subsets of a Zod enum with the `.exclude` and `.extract` methods. + +```ts +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); +const SalmonAndTrout = FishEnum.extract(["Salmon", "Trout"]); +const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]); +``` + +## Native enums + +Zod enums are the recommended approach to defining and validating enums. But if you need to validate against an enum from a third-party library (or you don't want to rewrite your existing enums) you can use `z.nativeEnum()`. + +**Numeric enums** + +```ts +enum Fruits { + Apple, + Banana, +} + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // Fruits + +FruitEnum.parse(Fruits.Apple); // passes +FruitEnum.parse(Fruits.Banana); // passes +FruitEnum.parse(0); // passes +FruitEnum.parse(1); // passes +FruitEnum.parse(3); // fails +``` + +**String enums** + +```ts +enum Fruits { + Apple = "apple", + Banana = "banana", + Cantaloupe, // you can mix numerical and string enums +} + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // Fruits + +FruitEnum.parse(Fruits.Apple); // passes +FruitEnum.parse(Fruits.Cantaloupe); // passes +FruitEnum.parse("apple"); // passes +FruitEnum.parse("banana"); // passes +FruitEnum.parse(0); // passes +FruitEnum.parse("Cantaloupe"); // fails +``` + +**Const enums** + +The `.nativeEnum()` function works for `as const` objects as well. ⚠️ `as const` requires TypeScript 3.4+! + +```ts +const Fruits = { + Apple: "apple", + Banana: "banana", + Cantaloupe: 3, +} as const; + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // "apple" | "banana" | 3 + +FruitEnum.parse("apple"); // passes +FruitEnum.parse("banana"); // passes +FruitEnum.parse(3); // passes +FruitEnum.parse("Cantaloupe"); // fails +``` + +You can access the underlying object with the `.enum` property: + +```ts +FruitEnum.enum.Apple; // "apple" +``` + +## Optionals + +You can make any schema optional with `z.optional()`. This wraps the schema in a `ZodOptional` instance and returns the result. + +```ts +const schema = z.optional(z.string()); + +schema.parse(undefined); // => returns undefined +type A = z.infer; // string | undefined +``` + +For convenience, you can also call the `.optional()` method on an existing schema. + +```ts +const user = z.object({ + username: z.string().optional(), +}); +type C = z.infer; // { username?: string | undefined }; +``` + +You can extract the wrapped schema from a `ZodOptional` instance with `.unwrap()`. + +```ts +const stringSchema = z.string(); +const optionalString = stringSchema.optional(); +optionalString.unwrap() === stringSchema; // true +``` + +## Nullables + +Similarly, you can create nullable types with `z.nullable()`. + +```ts +const nullableString = z.nullable(z.string()); +nullableString.parse("asdf"); // => "asdf" +nullableString.parse(null); // => null +``` + +Or use the `.nullable()` method. + +```ts +const E = z.string().nullable(); // equivalent to nullableString +type E = z.infer; // string | null +``` + +Extract the inner schema with `.unwrap()`. + +```ts +const stringSchema = z.string(); +const nullableString = stringSchema.nullable(); +nullableString.unwrap() === stringSchema; // true +``` + +## Objects + +```ts +// all properties are required by default +const Dog = z.object({ + name: z.string(), + age: z.number(), +}); + +// extract the inferred type like this +type Dog = z.infer; + +// equivalent to: +type Dog = { + name: string; + age: number; +}; +``` + +### `.shape` + +Use `.shape` to access the schemas for a particular key. + +```ts +Dog.shape.name; // => string schema +Dog.shape.age; // => number schema +``` + +### `.keyof` + +Use `.keyof` to create a `ZodEnum` schema from the keys of an object schema. + +```ts +const keySchema = Dog.keyof(); +keySchema; // ZodEnum<["name", "age"]> +``` + +### `.extend` + +You can add additional fields to an object schema with the `.extend` method. + +```ts +const DogWithBreed = Dog.extend({ + breed: z.string(), +}); +``` + +You can use `.extend` to overwrite fields! Be careful with this power! + +### `.merge` + +Equivalent to `A.extend(B.shape)`. + +```ts +const BaseTeacher = z.object({ students: z.array(z.string()) }); +const HasID = z.object({ id: z.string() }); + +const Teacher = BaseTeacher.merge(HasID); +type Teacher = z.infer; // => { students: string[], id: string } +``` + +> If the two schemas share keys, the properties of B overrides the property of A. The returned schema also inherits the "unknownKeys" policy (strip/strict/passthrough) and the catchall schema of B. + +### `.pick/.omit` + +Inspired by TypeScript's built-in `Pick` and `Omit` utility types, all Zod object schemas have `.pick` and `.omit` methods that return a modified version. Consider this Recipe schema: + +```ts +const Recipe = z.object({ + id: z.string(), + name: z.string(), + ingredients: z.array(z.string()), +}); +``` + +To only keep certain keys, use `.pick` . + +```ts +const JustTheName = Recipe.pick({ name: true }); +type JustTheName = z.infer; +// => { name: string } +``` + +To remove certain keys, use `.omit` . + +```ts +const NoIDRecipe = Recipe.omit({ id: true }); + +type NoIDRecipe = z.infer; +// => { name: string, ingredients: string[] } +``` + +### `.partial` + +Inspired by the built-in TypeScript utility type [Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype), the `.partial` method makes all properties optional. + +Starting from this object: + +```ts +const user = z.object({ + email: z.string(), + username: z.string(), +}); +// { email: string; username: string } +``` + +We can create a partial version: + +```ts +const partialUser = user.partial(); +// { email?: string | undefined; username?: string | undefined } +``` + +You can also specify which properties to make optional: + +```ts +const optionalEmail = user.partial({ + email: true, +}); +/* +{ + email?: string | undefined; + username: string +} +*/ +``` + +### `.deepPartial` + +The `.partial` method is shallow — it only applies one level deep. There is also a "deep" version: + +```ts +const user = z.object({ + username: z.string(), + location: z.object({ + latitude: z.number(), + longitude: z.number(), + }), + strings: z.array(z.object({ value: z.string() })), +}); + +const deepPartialUser = user.deepPartial(); + +/* +{ + username?: string | undefined, + location?: { + latitude?: number | undefined; + longitude?: number | undefined; + } | undefined, + strings?: { value?: string}[] +} +*/ +``` + +> Important limitation: deep partials only work as expected in hierarchies of objects, arrays, and tuples. + +### `.required` + +Contrary to the `.partial` method, the `.required` method makes all properties required. + +Starting from this object: + +```ts +const user = z + .object({ + email: z.string(), + username: z.string(), + }) + .partial(); +// { email?: string | undefined; username?: string | undefined } +``` + +We can create a required version: + +```ts +const requiredUser = user.required(); +// { email: string; username: string } +``` + +You can also specify which properties to make required: + +```ts +const requiredEmail = user.required({ + email: true, +}); +/* +{ + email: string; + username?: string | undefined; +} +*/ +``` + +### `.passthrough` + +By default Zod object schemas strip out unrecognized keys during parsing. + +```ts +const person = z.object({ + name: z.string(), +}); + +person.parse({ + name: "bob dylan", + extraKey: 61, +}); +// => { name: "bob dylan" } +// extraKey has been stripped +``` + +Instead, if you want to pass through unknown keys, use `.passthrough()` . + +```ts +person.passthrough().parse({ + name: "bob dylan", + extraKey: 61, +}); +// => { name: "bob dylan", extraKey: 61 } +``` + +### `.strict` + +By default Zod object schemas strip out unrecognized keys during parsing. You can _disallow_ unknown keys with `.strict()` . If there are any unknown keys in the input, Zod will throw an error. + +```ts +const person = z + .object({ + name: z.string(), + }) + .strict(); + +person.parse({ + name: "bob dylan", + extraKey: 61, +}); +// => throws ZodError +``` + +### `.strip` + +You can use the `.strip` method to reset an object schema to the default behavior (stripping unrecognized keys). + +### `.catchall` + +You can pass a "catchall" schema into an object schema. All unknown keys will be validated against it. + +```ts +const person = z + .object({ + name: z.string(), + }) + .catchall(z.number()); + +person.parse({ + name: "bob dylan", + validExtraKey: 61, // works fine +}); + +person.parse({ + name: "bob dylan", + validExtraKey: false, // fails +}); +// => throws ZodError +``` + +Using `.catchall()` obviates `.passthrough()` , `.strip()` , or `.strict()`. All keys are now considered "known". + +## Arrays + +```ts +const stringArray = z.array(z.string()); + +// equivalent +const stringArray = z.string().array(); +``` + +Be careful with the `.array()` method. It returns a new `ZodArray` instance. This means the _order_ in which you call methods matters. For instance: + +```ts +z.string().optional().array(); // (string | undefined)[] +z.string().array().optional(); // string[] | undefined +``` + +### `.element` + +Use `.element` to access the schema for an element of the array. + +```ts +stringArray.element; // => string schema +``` + +### `.nonempty` + +If you want to ensure that an array contains at least one element, use `.nonempty()`. + +```ts +const nonEmptyStrings = z.string().array().nonempty(); +// the inferred type is now +// [string, ...string[]] + +nonEmptyStrings.parse([]); // throws: "Array cannot be empty" +nonEmptyStrings.parse(["Ariana Grande"]); // passes +``` + +You can optionally specify a custom error message: + +```ts +// optional custom error message +const nonEmptyStrings = z.string().array().nonempty({ + message: "Can't be empty!", +}); +``` + +### `.min/.max/.length` + +```ts +z.string().array().min(5); // must contain 5 or more items +z.string().array().max(5); // must contain 5 or fewer items +z.string().array().length(5); // must contain 5 items exactly +``` + +Unlike `.nonempty()` these methods do not change the inferred type. + +### `.unique` + +```ts +// All elements must be unique +z.object({ id: z.string() }).array().unique(); + +// All elements must be unique based on the id property +z.object({ id: z.string(), name: z.string() }) + .array() + .unique({ identifier: (elt) => elt.id }); +``` + +## Tuples + +Unlike arrays, tuples have a fixed number of elements and each element can have a different type. + +```ts +const athleteSchema = z.tuple([ + z.string(), // name + z.number(), // jersey number + z.object({ + pointsScored: z.number(), + }), // statistics +]); + +type Athlete = z.infer; +// type Athlete = [string, number, { pointsScored: number }] +``` + +A variadic ("rest") argument can be added with the `.rest` method. + +```ts +const variadicTuple = z.tuple([z.string()]).rest(z.number()); +const result = variadicTuple.parse(["hello", 1, 2, 3]); +// => [string, ...number[]]; +``` + +## Unions + +Zod includes a built-in `z.union` method for composing "OR" types. + +```ts +const stringOrNumber = z.union([z.string(), z.number()]); + +stringOrNumber.parse("foo"); // passes +stringOrNumber.parse(14); // passes +``` + +Zod will test the input against each of the "options" in order and return the first value that validates successfully. + +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 +``` + +## Discriminated unions + +A discriminated union is a union of object schemas that all share a particular key. + +```ts +type MyUnion = + | { status: "success"; data: string } + | { status: "failed"; error: Error }; +``` + +Such unions can be represented with the `z.discriminatedUnion` method. This enables faster evaluation, because Zod can check the _discriminator key_ (`status` in the example above) to determine which schema should be used to parse the input. This makes parsing more efficient and lets Zod report friendlier errors. + +With the basic union method, the input is tested against each of the provided "options", and in the case of invalidity, issues for all the "options" are shown in the zod error. On the other hand, the discriminated union allows for selecting just one of the "options", testing against it, and showing only the issues related to this "option". + +```ts +const myUnion = z.discriminatedUnion("status", [ + z.object({ status: z.literal("success"), data: z.string() }), + z.object({ status: z.literal("failed"), error: z.instanceof(Error) }), +]); + +myUnion.parse({ status: "success", data: "yippie ki yay" }); +``` + +You can extract a reference to the array of schemas with the `.options` property. + +```ts +myUnion.options; // [ZodObject<...>, ZodObject<...>] +``` + +To merge two or more discriminated unions, use `.options` with destructuring. + +```ts +const A = z.discriminatedUnion("status", [ + /* options */ +]); +const B = z.discriminatedUnion("status", [ + /* options */ +]); + +const AB = z.discriminatedUnion("status", [...A.options, ...B.options]); +``` + +## Records + +Record schemas are used to validate types such as `Record`. This is particularly useful for storing or caching items by ID. + +{/* If you want to validate the _values_ of an object against some schema but don't care about the keys, use `z.record(valueType)`: + +```ts +const NumberCache = z.record(z.number()); + +type NumberCache = z.infer; +// => { [k: string]: number } +``` */} + +```ts +const User = z.object({ name: z.string() }); + +const UserStore = z.record(z.string(), User); +type UserStore = z.infer; +// => Record +``` + +The schema and inferred type can be used like so: + +```ts +const userStore: UserStore = {}; + +userStore["77d2586b-9e8e-4ecf-8b21-ea7e0530eadd"] = { + name: "Carlotta", +}; // passes + +userStore["77d2586b-9e8e-4ecf-8b21-ea7e0530eadd"] = { + whatever: "Ice cream sundae", +}; // TypeError +``` + +**A note on numerical keys** + +While `z.record(keyType, valueType)` is able to accept numerical key types and TypeScript's built-in Record type is `Record`, it's hard to represent the TypeScript type `Record` in Zod. + +As it turns out, TypeScript's behavior surrounding `[k: number]` is a little unintuitive: + +```ts +const testMap: { [k: number]: string } = { + 1: "one", +}; + +for (const key in testMap) { + console.log(`${key}: ${typeof key}`); +} +// prints: `1: string` +``` + +As you can see, JavaScript automatically casts all object keys to strings under the hood. Since Zod is trying to bridge the gap between static and runtime types, it doesn't make sense to provide a way of creating a record schema with numerical keys, since there's no such thing as a numerical key in runtime JavaScript. + +## Maps + +```ts +const stringNumberMap = z.map(z.string(), z.number()); + +type StringNumberMap = z.infer; +// type StringNumberMap = Map +``` + +## Sets + +```ts +const numberSet = z.set(z.number()); +type NumberSet = z.infer; +// type NumberSet = Set +``` + +Set schemas can be further constrained with the following utility methods. + +```ts +z.set(z.string()).nonempty(); // must contain at least one 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 +``` + +## Intersections + +Intersections are useful for creating "logical AND" types. This is useful for intersecting two object types. + +```ts +const Person = z.object({ + name: z.string(), +}); + +const Employee = z.object({ + role: z.string(), +}); + +const EmployedPerson = z.intersection(Person, Employee); + +// equivalent to: +const EmployedPerson = Person.and(Employee); +``` + +Though in many cases, it is recommended to use `A.merge(B)` to merge two objects. The `.merge` method returns a new `ZodObject` instance, whereas `A.and(B)` returns a less useful `ZodIntersection` instance that lacks common object methods like `pick` and `omit`. + +```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; // => number +``` + +{/* Intersections in Zod are not smart. Whatever data you pass into `.parse()` gets passed into the two intersected schemas. Because Zod object schemas don't allow any unknown keys by default, there are some unintuitive behavior surrounding intersections of object schemas. */} + +{/* + +``` ts +const A = z.object({ + a: z.string(), +}); + +const B = z.object({ + b: z.string(), +}); + +const AB = z.intersection(A, B); + +type Teacher = z.infer; +// { id:string; name:string }; +``` */} + +## Recursive types + +You can define a recursive schema in Zod, but because of a limitation of TypeScript, their type can't be statically inferred. Instead you'll need to define the type definition manually, and provide it to Zod as a "type hint". + +```ts +const baseCategorySchema = z.object({ + name: z.string(), +}); + +type Category = z.infer & { + subcategories: Category[]; +}; + +const categorySchema: z.ZodType = baseCategorySchema.extend({ + subcategories: z.lazy(() => categorySchema.array()), +}); + +categorySchema.parse({ + name: "People", + subcategories: [ + { + name: "Politicians", + subcategories: [ + { + name: "Presidents", + subcategories: [], + }, + ], + }, + ], +}); // passes +``` + +Thanks to [crasite](https://github.com/crasite) for this example. + +### ZodType with ZodTransform + +When using `z.ZodType` with `z.ZodTransform` ( +[`.refine`](https://github.com/colinhacks/zod#refine), +[`.transform`](https://github.com/colinhacks/zod#transform), +[`preprocess`](https://github.com/colinhacks/zod#preprocess), +etc... +), you will need to define the input and output types of the schema. `z.ZodType` + +```ts +const isValidId = (id: string): id is `${string}/${string}` => + id.split("/").length === 2; + +const baseSchema = z.object({ + id: z.string().refine(isValidId), +}); + +type Input = z.input & { + children: Input[]; +}; + +type Output = z.output & { + children: Output[]; +}; + +const schema: z.ZodType = baseSchema.extend({ + children: z.lazy(() => schema.array()), +}); +``` + +Thanks to [marcus13371337](https://github.com/marcus13371337) and [JoelBeeldi](https://github.com/JoelBeeldi) for this example. + +### JSON type + +If you want to validate any JSON value, you can use the snippet below. + +```ts +const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); +type Literal = z.infer; +type Json = Literal | { [key: string]: Json } | Json[]; +const jsonSchema: z.ZodType = z.lazy(() => + z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]) +); + +jsonSchema.parse(data); +``` + +Thanks to [ggoodman](https://github.com/ggoodman) for suggesting this. + +### Cyclical objects + +Despite supporting recursive schemas, passing cyclical data into Zod will cause an infinite loop in some cases. + +> To detect cyclical objects before they cause problems, consider [this approach](https://gist.github.com/colinhacks/d35825e505e635df27cc950776c5500b). + +## Promises + +```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 + +const test = async () => { + await numberPromise.parse(Promise.resolve("tuna")); + // ZodError: Non-number type: string + + await numberPromise.parse(Promise.resolve(3.14)); + // => 3.14 +}; +``` + +{/* #### Non-native promise implementations + +When "parsing" a promise, Zod checks that the passed value is an object with `.then` and `.catch` methods — that's it. So you should be able to pass non-native Promises (Bluebird, etc) into `z.promise(...).parse` with no trouble. One gotcha: the return type of the parse function will be a _native_ `Promise` , so if you have downstream logic that uses non-standard Promise methods, this won't work. */} + +## 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); + +const blob: any = "whatever"; +TestSchema.parse(new Test()); // passes +TestSchema.parse(blob); // throws +``` + +## Functions + +Zod also lets you define "function schemas". This makes it easy to validate the inputs and outputs of a function without intermixing your validation code and "business logic". + +You can create a function schema with `z.function(args, returnType)` . + +```ts +const myFunction = z.function(); + +type myFunction = z.infer; +// => ()=>unknown +``` + +Define inputs and outputs. + +```ts +const myFunction = z + .function() + .args(z.string(), z.number()) // accepts an arbitrary number of arguments + .returns(z.boolean()); + +type myFunction = z.infer; +// => (arg0: string, arg1: number)=>boolean +``` + +{/* + +``` ts +const args = z.tuple([z.string()]); + +const returnType = z.number(); + +const myFunction = z.function(args, returnType); +type myFunction = z.infer; +// => (arg0: 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 trimmedLength = z + .function() + .args(z.string()) // accepts an arbitrary number of arguments + .returns(z.number()) + .implement((x) => { + // TypeScript knows x is a string! + return x.trim().length; + }); + +trimmedLength("sandwich"); // => 8 +trimmedLength(" asdf "); // => 4 +``` + +If you only care about validating inputs, just don't call the `.returns()` method. The output type will be inferred from the implementation. + +> You can use the special `z.void()` option if your function doesn't return anything. This will let Zod properly infer the type of void-returning functions. (Void-returning functions actually return undefined.) + +```ts +const myFunction = z + .function() + .args(z.string()) + .implement((arg) => { + return [arg.length]; + }); + +myFunction; // (arg: string)=>number[] +``` + +Extract the input and output schemas from a function schema. + +```ts +myFunction.parameters(); +// => ZodTuple<[ZodString, ZodNumber]> + +myFunction.returnType(); +// => ZodBoolean +``` + +{/* `z.function()` accepts two arguments: + +* `args: ZodTuple` The first argument is a tuple (created with `z.tuple([...])` and defines the schema of the arguments to your function. If the function doesn't accept arguments, you can pass an empty tuple (`z.tuple([])`). +* `returnType: any Zod schema` The second argument is the function's return type. This can be any Zod schema. */} + +## Template literals + +TypeScript supports [template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html), which are strings that conform to a statically known structure. + +```ts +type simpleTemplate = `Hello, ${string}!`; +type urlTemplate = `${"http" | "https"}://${string}.${`com` | `net`}`; +type pxTemplate = `${number}px`; +``` + +These types can be represented in Zod with `z.literal.template()`. Template literals consist of interleaved _literals_ and _schemas_. + +```ts +z.literal.template(["Hello, ", z.string()]); // infers to `Hello ${string}` +``` + +The literal components can be any string, number, boolean, null, or undefined. + +```ts +z.literal.template(["Hello", 3.14, true, null, undefined]); +// infers to `Hello3.14truenullundefined` +``` + +The schema components can be any literal, primitive, or enum schema. + +> **Note** — Refinements, transforms, and pipes are not supported. + +```ts +z.template.literal([ + z.string(), + z.number(), + z.boolean(), + z.bigint(), + z.any(), + z.literal("foo"), + z.null(), + z.undefined(), + z.enum(["bar"]), +]); +``` + +For "union" types like `z.boolean()` or `z.enum()`, the inferred static type will be a union of the possible values. + +```ts +z.literal.template([z.boolean(), z.number()]); +// `true${number}` | `false${number}` + +z.literal.template(["is_", z.enum(["red", "green", "blue"])]); +// `is_red` | `is_green` | `is_blue` +``` + +### Examples + +URL: + +```ts +const url = z.literal.template([ + "https://", + z.string(), + ".", + z.enum(["com", "net"]), +]); +// infers to `https://${string}.com` | `https://${string}.net`. + +url.parse("https://google.com"); // passes +url.parse("https://google.net"); // passes +url.parse("http://google.com"); // throws +url.parse("https://.com"); // throws +url.parse("https://google"); // throws +url.parse("https://google."); // throws +url.parse("https://google.gov"); // throws +``` + +CSS Measurement: + +```ts +const measurement = z.literal.template([ + z.number().finite(), + z.enum(["px", "em", "rem", "vh", "vw", "vmin", "vmax"]), +]); +// infers to `${number}` | `${number}px` | `${number}em` | `${number}rem` | `${number}vh` | `${number}vw` | `${number}vmin` | `${number}vmax +``` + +MongoDB connection string: + +```ts +const connectionString = z.literal.template([ + "mongodb://", + z.literal + .template([ + z.string().regex(/\w+/).describe("username"), + ":", + z.string().regex(/\w+/).describe("password"), + "@", + ]) + .optional(), + z.string().regex(/\w+/).describe("host"), + ":", + z.number().finite().int().positive().describe("port"), + z.literal + .template([ + "/", + z.string().regex(/\w+/).optional().describe("defaultauthdb"), + z.literal + .template(["?", z.string().regex(/^\w+=\w+(&\w+=\w+)*$/)]) + .optional() + .describe("options"), + ]) + .optional(), +]); +// inferred type: +// | `mongodb://${string}:${number}` +// | `mongodb://${string}:${string}@${string}:${number}` +// | `mongodb://${string}:${number}/${string}` +// | `mongodb://${string}:${string}@${string}:${number}/${string}` +// | `mongodb://${string}:${number}/${string}?${string}` +// | `mongodb://${string}:${string}@${string}:${number}/${string}?${string}`; +``` + +## Preprocess + +> Zod now supports primitive coercion without the need for `.preprocess()`. See the [coercion docs](#coercion-for-primitives) for more information. + +Typically Zod operates under a "parse then transform" paradigm. Zod validates the input first, then passes it through a chain of transformation functions. (For more information about transforms, read the [.transform docs](#transform).) + +But sometimes you want to apply some transform to the input _before_ parsing happens. A common use case: type coercion. Zod enables this with the `z.preprocess()`. + +```ts +const castToString = z.preprocess((val) => String(val), z.string()); +``` + +This returns a `ZodTransform` instance. `ZodTransform` is a wrapper class that contains all logic pertaining to preprocessing, refinements, and transforms. + +## Custom schemas + +You can create a Zod schema for any TypeScript type by using `z.custom()`. This is useful for creating schemas for types that are not supported by Zod out of the box, such as template string literals. + +```ts +const px = z.custom<`${number}px`>((val) => { + return typeof val === "string" ? /^\d+px$/.test(val) : false; +}); + +type px = z.infer; // `${number}px` + +px.parse("42px"); // "42px" +px.parse("42vw"); // 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"); +``` + +## Schema methods + +All Zod schemas contain certain methods. + +### `.parse` + +`.parse(data: unknown): T` + +Given any Zod schema, you can call its `.parse` method to check `data` is valid. If it is, a value is returned with full type information! Otherwise, an error is thrown. + +> IMPORTANT: The value returned by `.parse` is a _deep clone_ of the variable you passed in. + +```ts +const stringSchema = z.string(); + +stringSchema.parse("fish"); // => returns "fish" +stringSchema.parse(12); // throws error +``` + +### `.parseAsync` + +`.parseAsync(data:unknown): Promise` + +If you use asynchronous [refinements](#refine) or [transforms](#transform) (more on those later), you'll need to use `.parseAsync`. + +```ts +const stringSchema = z.string().refine(async (val) => val.length <= 8); + +await stringSchema.parseAsync("hello"); // => returns "hello" +await stringSchema.parseAsync("hello world"); // => throws error +``` + +### `.safeParse` + +`.safeParse(data:unknown): { success: true; data: T; } | { success: false; error: ZodError; }` + +If you don't want Zod to throw errors when validation fails, use `.safeParse`. This method returns an object containing either the successfully parsed data or a ZodError instance containing detailed information about the validation problems. + +```ts +stringSchema.safeParse(12); +// => { success: false; error: ZodError } + +stringSchema.safeParse("billie"); +// => { success: true; data: 'billie' } +``` + +The result is a _discriminated union_, so you can handle errors very conveniently: + +```ts +const result = stringSchema.safeParse("billie"); +if (!result.success) { + // handle error then return + result.error; +} else { + // do something + result.data; +} +``` + +### `.safeParseAsync` + +> Alias: `.spa` + +An asynchronous version of `safeParse`. + +```ts +await stringSchema.safeParseAsync("billie"); +``` + +For convenience, this has been aliased to `.spa`: + +```ts +await stringSchema.spa("billie"); +``` + +### `.refine` + +`.refine(validator: (data:T)=>any, params?: RefineParams)` + +Zod lets you provide custom validation logic via _refinements_. (For advanced features like creating multiple issues and customizing error codes, see [`.superRefine`](#superrefine).) + +Zod was designed to mirror TypeScript as closely as possible. But there are many so-called "refinement types" you may wish to check for that can't be represented in TypeScript's type system. For instance: checking that a number is an integer or that a string is a valid email address. + +For example, you can define a custom validation check on _any_ Zod schema with `.refine` : + +```ts +const myString = z.string().refine((val) => val.length <= 255, { + message: "String can't be more than 255 characters", +}); +``` + +> ⚠️ Refinement functions should not throw. Instead they should return a falsy value to signal failure. + +#### Arguments + +As you can see, `.refine` takes two arguments. + +1. The first is the validation function. This function takes one input (of type `T` — the inferred type of the schema) and returns `any`. Any truthy value will pass validation. (Prior to zod@1.6.2 the validation function had to return a boolean.) +2. The second argument accepts some options. You can use this to customize certain error-handling behavior: + +```ts +type RefineParams = { + // override error message + message?: string; + + // appended to error path + path?: (string | number)[]; + + // params object you can use to customize message + // in error map + params?: object; +}; +``` + +For advanced cases, the second argument can also be a function that returns `RefineParams`. + +```ts +const longString = z.string().refine( + (val) => val.length > 10, + (val) => ({ message: `${val} is not more than 10 characters` }) +); +``` + +#### Customize error path + +```ts +const passwordForm = z + .object({ + password: z.string(), + confirm: z.string(), + }) + .refine((data) => data.password === data.confirm, { + message: "Passwords don't match", + path: ["confirm"], // path of error + }); + +passwordForm.parse({ password: "asdf", confirm: "qwer" }); +``` + +Because you provided a `path` parameter, the resulting error will be: + +```ts +ZodError { + issues: [{ + "code": "custom", + "path": [ "confirm" ], + "message": "Passwords don't match" + }] +} +``` + +#### Asynchronous refinements + +Refinements can also be async: + +```ts +const userId = z.string().refine(async (id) => { + // verify that ID exists in database + return true; +}); +``` + +> ⚠️ If you use async refinements, you must use the `.parseAsync` method to parse data! Otherwise Zod will throw an error. + +#### Relationship to transforms + +Transforms and refinements can be interleaved: + +```ts +z.string() + .transform((val) => val.length) + .refine((val) => val > 25); +``` + +{/* Note that the `path` is set to `["confirm"]` , so you can easily display this error underneath the "Confirm password" textbox. + +```ts +const allForms = z.object({ passwordForm }).parse({ + passwordForm: { + password: "asdf", + confirm: "qwer", + }, +}); +``` + +would result in + +``` + +ZodError { + issues: [{ + "code": "custom", + "path": [ "passwordForm", "confirm" ], + "message": "Passwords don't match" + }] +} +``` */} + +### `.superRefine` + +The `.refine` method is actually syntactic sugar atop a more versatile (and verbose) method called `superRefine`. Here's an example: + +```ts +const Strings = z.array(z.string()).superRefine((val, ctx) => { + if (val.length > 3) { + ctx.addIssue({ + code: z.ZodIssueCode.too_big, + maximum: 3, + type: "array", + inclusive: true, + message: "Too many items 😡", + }); + } + + if (val.length !== new Set(val).size) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `No duplicates allowed.`, + }); + } +}); +``` + +You can add as many issues as you like. If `ctx.addIssue` is _not_ called during the execution of the function, validation passes. + +Normally refinements always create issues with a `ZodIssueCode.custom` error code, but with `superRefine` it's possible to throw issues of any `ZodIssueCode`. Each issue code is described in detail in the Error Handling guide: [ERROR_HANDLING.md](ERROR_HANDLING.md). + +#### Abort early + +By default, parsing will continue even after a refinement check fails. For instance, if you chain together multiple refinements, they will all be executed. However, it may be desirable to _abort early_ to prevent later refinements from being executed. To achieve this, pass the `fatal` flag to `ctx.addIssue` and return `z.NEVER`. + +```ts +const schema = z.number().superRefine((val, ctx) => { + if (val < 10) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "should be >= 10", + fatal: true, + }); + + return z.NEVER; + } + + if (val !== 12) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "should be twelve", + }); + } +}); +``` + +#### Type refinements + +If you provide a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) to `.refine()` or `.superRefine()`, the resulting type will be narrowed down to your predicate's type. This is useful if you are mixing multiple chained refinements and transformations: + +```ts +const schema = z + .object({ + first: z.string(), + second: z.number(), + }) + .nullable() + .superRefine((arg, ctx): arg is { first: string; second: number } => { + if (!arg) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, // customize your issue + message: "object should exist", + }); + } + + return z.NEVER; // The return value is not used, but we need to return something to satisfy the typing + }) + // here, TS knows that arg is not null + .refine((arg) => arg.first === "bob", "`first` is not `bob`!"); +``` + +> ⚠️ You **must** use `ctx.addIssue()` instead of returning a boolean value to indicate whether the validation passes. If `ctx.addIssue` is _not_ called during the execution of the function, validation passes. + +### `.transform` + +To transform data after parsing, use the `transform` method. + +```ts +const stringToNumber = z.string().transform((val) => val.length); + +stringToNumber.parse("string"); // => 6 +``` + +#### Chaining order + +Note that `stringToNumber` above is an instance of the `ZodTransform` subclass. It is NOT an instance of `ZodString`. If you want to use the built-in methods of `ZodString` (e.g. `.email()`) you must apply those methods _before_ any transforms. + +```ts +const emailToDomain = z + .string() + .email() + .transform((val) => val.split("@")[1]); + +emailToDomain.parse("colinhacks@example.com"); // => example.com +``` + +#### Validating during transform + +The `.transform` method can simultaneously validate and transform the value. This is often simpler and less duplicative than chaining `transform` and `refine`. + +As with `.superRefine`, the transform function receives a `ctx` object with an `addIssue` method that can be used to register validation issues. + +```ts +const numberInString = z.string().transform((val, ctx) => { + const parsed = parseInt(val); + if (isNaN(parsed)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Not a number", + }); + + // This is a special symbol you can use to + // return early from the transform function. + // It has type `never` so it does not affect the + // inferred return type. + return z.NEVER; + } + return parsed; +}); +``` + +#### Relationship to refinements + +Transforms and refinements can be interleaved. These will be executed in the order they are declared. + +```ts +const nameToGreeting = z + .string() + .transform((val) => val.toUpperCase()) + .refine((val) => val.length > 15) + .transform((val) => `Hello ${val}`) + .refine((val) => val.indexOf("!") === -1); +``` + +#### Async transforms + +Transforms can also be async. + +```ts +const IdToUser = z + .string() + .uuid() + .transform(async (id) => { + return await getUserById(id); + }); +``` + +> ⚠️ If your schema contains asynchronous transforms, you must use .parseAsync() or .safeParseAsync() to parse data. Otherwise Zod will throw an error. + +### `.default` + +You can use transforms to implement the concept of "default values" in Zod. + +```ts +const stringWithDefault = z.string().default("tuna"); + +stringWithDefault.parse(undefined); // => "tuna" +``` + +Optionally, you can pass a function into `.default` that will be re-executed whenever a default value needs to be generated: + +```ts +const numberWithRandomDefault = z.number().default(Math.random); + +numberWithRandomDefault.parse(undefined); // => 0.4413456736055323 +numberWithRandomDefault.parse(undefined); // => 0.1871840107401901 +numberWithRandomDefault.parse(undefined); // => 0.7223408162401552 +``` + +Conceptually, this is how Zod processes default values: + +1. If the input is `undefined`, the default value is returned +2. Otherwise, the data is parsed using the base schema + +### `.describe` + +Use `.describe()` to add a `description` property to the resulting schema. + +```ts +const documentedString = z + .string() + .describe("A useful bit of text, if you know what to do with it."); +documentedString.description; // A useful bit of text… +``` + +This can be useful for documenting a field, for example in a JSON Schema using a library like [`zod-to-json-schema`](https://github.com/StefanTerdell/zod-to-json-schema)). + +### `.catch` + +Use `.catch()` to provide a "catch value" to be returned in the event of a parsing error. + +```ts +const numberWithCatch = z.number().catch(42); + +numberWithCatch.parse(5); // => 5 +numberWithCatch.parse("tuna"); // => 42 +``` + +Optionally, you can pass a function into `.catch` that will be re-executed whenever a default value needs to be generated. A `ctx` object containing the caught error will be passed into this function. + +```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 +``` + +Conceptually, this is how Zod processes "catch values": + +1. The data is parsed using the base schema +2. If the parsing fails, the "catch value" is returned + +### `.optional` + +A convenience method that returns an optional version of a schema. + +```ts +const optionalString = z.string().optional(); // string | undefined + +// equivalent to +z.optional(z.string()); +``` + +### `.nullable` + +A convenience method that returns a nullable version of a schema. + +```ts +const nullableString = z.string().nullable(); // string | null + +// equivalent to +z.nullable(z.string()); +``` + +### `.nullish` + +A convenience method that returns a "nullish" version of a schema. Nullish schemas will accept both `undefined` and `null`. Read more about the concept of "nullish" [in the TypeScript 3.7 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing). + +```ts +const nullishString = z.string().nullish(); // string | null | undefined + +// equivalent to +z.string().nullable().optional(); +``` + +### `.array` + +A convenience method that returns an array schema for the given type: + +```ts +const stringArray = z.string().array(); // string[] + +// equivalent to +z.array(z.string()); +``` + +### `.promise` + +A convenience method for promise types: + +```ts +const stringPromise = z.string().promise(); // Promise + +// equivalent to +z.promise(z.string()); +``` + +### `.or` + +A convenience method for [union types](#unions). + +```ts +const stringOrNumber = z.string().or(z.number()); // string | number + +// equivalent to +z.union([z.string(), z.number()]); +``` + +### `.and` + +A convenience method for creating intersection types. + +```ts +const nameAndAge = z + .object({ name: z.string() }) + .and(z.object({ age: z.number() })); // { name: string } & { age: number } + +// equivalent to +z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() })); +``` + +### `.brand` + +`.brand() => ZodBranded` + +TypeScript's type system is structural, which means that any two types that are structurally equivalent are considered the same. + +```ts +type Cat = { name: string }; +type Dog = { name: string }; + +const petCat = (cat: Cat) => {}; +const fido: Dog = { name: "fido" }; +petCat(fido); // works fine +``` + +In some cases, its can be desirable to simulate _nominal typing_ inside TypeScript. For instance, you may wish to write a function that only accepts an input that has been validated by Zod. This can be achieved with _branded types_ (AKA _opaque types_). + +```ts +const Cat = z.object({ name: z.string() }).brand<"Cat">(); +type Cat = z.infer; + +const petCat = (cat: Cat) => {}; + +// this works +const simba = Cat.parse({ name: "simba" }); +petCat(simba); + +// this doesn't +petCat({ name: "fido" }); +``` + +Under the hood, this works by attaching a "brand" to the inferred type using an intersection type. This way, plain/unbranded data structures are no longer assignable to the inferred type of the schema. + +```ts +const Cat = z.object({ name: z.string() }).brand<"Cat">(); +type Cat = z.infer; +// {name: string} & {[symbol]: "Cat"} +``` + +Note that branded types do not affect the runtime result of `.parse`. It is a static-only construct. + +### `.readonly` + +`.readonly() => ZodReadonly` + +This method returns a `ZodReadonly` schema instance that parses the input using the base schema, then calls `Object.freeze()` on the result. The inferred type is also marked as `readonly`. + +```ts +const schema = z.object({ name: z.string() }).readonly(); +type schema = z.infer; +// Readonly<{name: string}> + +const result = schema.parse({ name: "fido" }); +result.name = "simba"; // error +``` + +The inferred type uses TypeScript's built-in readonly types when relevant. + +```ts +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 + +z.set(z.string()).readonly(); +// ReadonlySet +``` + +### `.pipe` + +Schemas can be chained into validation "pipes". It's useful for easily validating the result after a `.transform()`: + +```ts +z.string() + .transform((val) => val.length) + .pipe(z.number().min(5)); +``` + +The `.pipe()` method returns a `ZodPipe` instance. + +#### You can use `.pipe()` to fix common issues with `z.coerce`. + +You can constrain the input to types that work well with your chosen coercion. Then use `.pipe()` to apply the coercion. + +without constrained input: + +```ts +const toDate = z.coerce.date(); + +// works intuitively +console.log(toDate.safeParse("2023-01-01").success); // true + +// might not be what you want +console.log(toDate.safeParse(null).success); // true +``` + +with constrained input: + +```ts +const datelike = z.union([z.number(), z.string(), z.date()]); +const datelikeToDate = datelike.pipe(z.coerce.date()); + +// still works intuitively +console.log(datelikeToDate.safeParse("2023-01-01").success); // true + +// more likely what you want +console.log(datelikeToDate.safeParse(null).success); // false +``` + +You can also use this technique to avoid coercions that throw uncaught errors. + +without constrained input: + +```ts +const toBigInt = z.coerce.bigint(); + +// works intuitively +console.log(toBigInt.safeParse("42")); // true + +// probably not what you want +console.log(toBigInt.safeParse(null)); // throws uncaught error +``` + +with constrained input: + +```ts +const toNumber = z.number().or(z.string()).pipe(z.coerce.number()); +const toBigInt = z.bigint().or(toNumber).pipe(z.coerce.bigint()); + +// still works intuitively +console.log(toBigInt.safeParse("42").success); // true + +// error handled by zod, more likely what you want +console.log(toBigInt.safeParse(null).success); // false +``` + +## Guides and concepts + +### Type inference + +You can extract the TypeScript type of any schema with `z.infer` . + +```ts +const A = z.string(); +type A = z.infer; // string + +const u: A = 12; // TypeError +const u: A = "asdf"; // compiles +``` + +**What about transforms?** + +In reality each Zod schema internally tracks **two** types: an input and an output. For most schemas (e.g. `z.string()`) these two are the same. But once you add transforms into the mix, these two values can diverge. For instance `z.string().transform(val => val.length)` has an input of `string` and an output of `number`. + +You can separately extract the input and output types like so: + +```ts +const stringToNumber = z.string().transform((val) => val.length); + +// ⚠️ Important: z.infer returns the OUTPUT type! +type input = z.input; // string +type output = z.output; // number + +// equivalent to z.output! +type inferred = z.infer; // number +``` + +### Writing generic functions + +With TypeScript generics, you can write reusable functions that accept Zod schemas as parameters. This enables you to create custom validation logic, schema transformations, and more, while maintaining type safety and inference. + +When attempting to write a function that accepts a Zod schema as an input, it's tempting to try something like this: + +```ts +function inferSchema(schema: z.ZodType) { + 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 +``` + +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`. + +A better approach is to infer _the schema as a whole_ instead of merely its inferred type. + +```ts +function inferSchema(schema: T) { + return schema; +} + +inferSchema(z.string()); +// => ZodString +``` + +The Result is now fully and properly typed, and the type system can infer the specific subclass of the schema. + +#### Inferrence in generic functions + +In the general case, any function that accepts a generic Zod schema as an argument should be explicitly typed. + +```ts +// doesn't work! +function parseData(data: unknown, schema: T) { + return schema.parse(data); +} + +parseData("sup", z.string()); +// => any +``` + +Note that the return type of `parseData` is `any` instead of `string` as you might expect. There are limits to how TypeScript inference works inside generic functions. Generally speaking, you will need to write an explicit output type. + +```ts +function parseData( + data: unknown, + schema: T +): z.infer { + return schema.parse(data); +} + +parseData("sup", z.string()); +// => string +``` + +#### Constraining allowable inputs + +The `ZodType` class has three generic parameters. + +```ts +class ZodType< + Output = any, + Def extends ZodTypeDef = ZodTypeDef, + Input = Output +> { ... } +``` + +By constraining these in your generic input, you can limit what schemas are allowable as inputs to your function: + +```ts +function makeSchemaOptional>(schema: T) { + return schema.optional(); +} + +makeSchemaOptional(z.string()); +// works fine + +makeSchemaOptional(z.number()); +// Error: 'ZodNumber' is not assignable to parameter of type 'ZodType' +``` + +### Error handling + +Zod provides a subclass of Error called `ZodError`. ZodErrors contain an `issues` array containing detailed information about the validation problems. + +```ts +const result = z + .object({ + name: z.string(), + }) + .safeParse({ name: 12 }); + +if (!result.success) { + result.error.issues; + /* [ + { + "code": "invalid_type", + "expected": "string", + "received": "number", + "path": [ "name" ], + "message": "Expected string, received number" + } + ] */ +} +``` + +> For detailed information about the possible error codes and how to customize error messages, check out the dedicated error handling guide: [ERROR_HANDLING.md](ERROR_HANDLING.md) + +Zod's error reporting emphasizes _completeness_ and _correctness_. If you are looking to present a useful error message to the end user, you should either override Zod's error messages using an error map (described in detail in the Error Handling guide) or use a third-party library like [`zod-validation-error`](https://github.com/causaly/zod-validation-error) + +### Error formatting + +You can use the `.format()` method to convert this error into a nested object. + +```ts +const result = z + .object({ + name: z.string(), + }) + .safeParse({ name: 12 }); + +if (!result.success) { + const formatted = result.error.format(); + /* { + name: { _errors: [ 'Expected string, received number' ] } + } */ + + formatted.name?._errors; + // => ["Expected string, received number"] +} +``` + +## Comparison + +There are a handful of other widely-used validation libraries, but all of them have certain design limitations that make for a non-ideal developer experience. + +{/* The table below summarizes the feature differences. Below the table there are more involved discussions of certain alternatives, where necessary. */} + +{/* | Feature | [Zod](https://github.com/colinhacks) | [Joi](https://github.com/hapijs/joi) | [Yup](https://github.com/jquense/yup) | [io-ts](https://github.com/gcanti/io-ts) | [Runtypes](https://github.com/pelotom/runtypes) | [ow](https://github.com/sindresorhus/ow) | [class-validator](https://github.com/typestack/class-validator) | +| ---------------------------------------------------------------------------------------------------------------------- | :-----------------------------: | :----------------------------------: | :-----------------------------------: | :--------------------------------------: | :---------------------------------------------: | :--------------------------------------: | :-------------------------------------------------------------: | +| Type inference | 🟢 | 🔴 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| Correct type inference | 🟢 | 🔴 | 🔴 | 🟢 | 🟢 | 🟢 | 🟢 | + +Primitive Types +String Validation +Number Validation +Dates + +Primitive Literals +Object Literals +Tuple Literals +Objects +Arrays +Non-empty arrays +Unions +Optionals +Nullable +Enums +Enum Autocomplete +Intersections +Object Merging +Tuples +Recursive Types +Function Schemas + +Validation Messages +Immutable instances +Type Guards +Validity Checking +Casting +Default Values +Rich Errors +Branded */} + +{/* - Missing object methods: (pick, omit, partial, deepPartial, merge, extend) + +* Missing nonempty arrays with proper typing (`[T, ...T[]]`) +* Missing lazy/recursive types +* Missing promise schemas +* Missing function schemas +* Missing union & intersection schemas +* Missing support for parsing cyclical data (maybe) +* Missing error customization */} + +### Joi + +[https://github.com/hapijs/joi](https://github.com/hapijs/joi) + +Doesn't support static type inference 😕 + +### Yup + +[https://github.com/jquense/yup](https://github.com/jquense/yup) + +Yup is a full-featured library that was implemented first in vanilla JS, and later rewritten in TypeScript. + +- Supports casting and transforms +- All object fields are optional by default +{/* - Missing nonempty arrays with proper typing (`[T, ...T[]]`) */} +- Missing promise schemas +- Missing function schemas +- Missing union & intersection schemas + +{/* ¹Yup has a strange interpretation of the word `required`. Instead of meaning "not undefined", Yup uses it to mean "not empty". So `yup.string().required()` will not accept an empty string, and `yup.array(yup.string()).required()` will not accept an empty array. Instead, Yup us Zod arrays there is a dedicated `.nonempty()` method to indicate this, or you can implement it with a custom refinement. */} + +### io-ts + +[https://github.com/gcanti/io-ts](https://github.com/gcanti/io-ts) + +io-ts is an excellent library by gcanti. The API of io-ts heavily inspired the design of Zod. + +In our experience, io-ts prioritizes functional programming purity over developer experience in many cases. This is a valid and admirable design goal, but it makes io-ts particularly hard to integrate into an existing codebase with a more procedural or object-oriented bias. For instance, consider how to define an object with optional properties in io-ts: + +```ts +import * as t from "io-ts"; + +const A = t.type({ + foo: t.string, +}); + +const B = t.partial({ + bar: t.number, +}); + +const C = t.intersection([A, B]); + +type C = t.TypeOf; +// returns { foo: string; bar?: number | undefined } +``` + +You must define the required and optional props in separate object validators, pass the optionals through `t.partial` (which marks all properties as optional), then combine them with `t.intersection` . + +Consider the equivalent in Zod: + +```ts +const C = z.object({ + foo: z.string(), + bar: z.number().optional(), +}); + +type C = z.infer; +// returns { foo: string; bar?: number | undefined } +``` + +This more declarative API makes schema definitions vastly more concise. + +`io-ts` also requires the use of gcanti's functional programming library `fp-ts` to parse results and handle errors. This is another fantastic resource for developers looking to keep their codebase strictly functional. But depending on `fp-ts` necessarily comes with a lot of intellectual overhead; a developer has to be familiar with functional programming concepts and the `fp-ts` nomenclature to use the library. + +- Supports codecs with serialization & deserialization transforms +- Supports branded types +- Supports advanced functional programming, higher-kinded types, `fp-ts` compatibility +- Missing object methods: (pick, omit, partial, deepPartial, merge, extend) +- Missing nonempty arrays with proper typing (`[T, ...T[]]`) +- Missing promise schemas +- Missing function schemas + +### Runtypes + +[https://github.com/pelotom/runtypes](https://github.com/pelotom/runtypes) + +Good type inference support. + +- Supports "pattern matching": computed properties that distribute over unions +- Missing object methods: (deepPartial, merge) +- Missing nonempty arrays with proper typing (`[T, ...T[]]`) +- Missing promise schemas +- Missing error customization + +### Ow + +[https://github.com/sindresorhus/ow](https://github.com/sindresorhus/ow) + +Ow is focused on function input validation. It's a library that makes it easy to express complicated assert statements, but it doesn't let you parse untyped data. They support a much wider variety of types; Zod has a nearly one-to-one mapping with TypeScript's type system, whereas ow lets you validate several highly-specific types out of the box (e.g. `int32Array` , see full list in their README). + +If you want to validate function inputs, use function schemas in Zod! It's a much simpler approach that lets you reuse a function type declaration without repeating yourself (namely, copy-pasting a bunch of ow assertions at the beginning of every function). Also Zod lets you validate your return types as well, so you can be sure there won't be any unexpected data passed downstream. + +## Changelog + +View the changelog at [CHANGELOG.md](CHANGELOG.md) diff --git a/packages/docs/content/api.mdx b/packages/docs/content/api.mdx new file mode 100644 index 0000000000..ac938ed2f2 --- /dev/null +++ b/packages/docs/content/api.mdx @@ -0,0 +1,2638 @@ +--- +title: 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 { z } from "zod"; + +// primitive types +z.string(); +z.number(); +z.bigint(); +z.boolean(); +z.symbol(); +z.undefined(); +z.null(); +``` + +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" +``` + + + + Zod coerces all inputs using the built-in constructors. + + | Zod API | Coercion | + |--------------------------|----------------------------| + | `z.coerce.string()` | `new String(value)` | + | `z.coerce.number()` | `new Number(value)` | + | `z.coerce.boolean()` | `new 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). + + + + +## 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); +const terrific = z.literal(Symbol("terrific")); +``` + +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"]); + +colos.parse("green"); // ✅ +colors.parse("yellow"); // ❌ +``` + +To extract the set of allowed values from a literal schema: + + + +```ts +colors.values; // => Set<"red" | "green" | "blue"> +``` + + +```ts +// no equivalent +``` + + + +## Strings + +{/* Zod provides a handful of built-in string validation and transform APIs. + + + +```ts +z.string().startsWith("fourscore") +``` + + +```ts @zod/mini +z.string().check(z.startsWith("fourscore")) +``` + + + +All of the APIs documented below support the `error` parameter for customizing the error message. + + + +```ts +z.string().startsWith("fourscore", {error: "Nice try, buddy"}) +``` + + +```ts @zod/mini +z.string().check(z.startsWith("fourscore", {error: "Nice try, buddy"})) +``` + */} + + +Zod provides a handful of built-in string validation and transform APIs. To perform some common string validations: + + + +```ts +z.string().max(5); +z.string().min(5); +z.string().length(5); +z.string().regex(/^[a-z]+$/); +z.string().startsWith("aaa"); +z.string().endsWith("zzz"); +z.string().includes("---"); +z.string().uppercase(); +z.string().lowercase(); +``` + + +```ts @zod/mini +z.string().check(z.max(5)); +z.string().check(z.min(5)); +z.string().check(z.length(5)); +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()); +``` + + + +To perform some simple string transforms: + + + +```ts +z.string().trim(); // trim whitespace +z.string().toLowerCase(); // toLowerCase +z.string().toUpperCase(); // toUpperCase +``` + + +```ts @zod/mini +z.string().check(z.trim()); // trim whitespace +z.string().check(z.toLowerCase()); // toLowerCase +z.string().check(z.toUpperCase()); // toUpperCase +``` + + + +## String formats + +To validate against some common string formats: + +```ts +z.email(); +z.uuid(); +z.url(); +z.emoji(); // validates a single emoji character +z.base64(); +z.nanoid(); +z.cuid(); +z.cuid2(); +z.ulid(); +z.ipv4(); +z.ipv6(); +z.cidr(); // ip range +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({ pattern: z.regexes.emai }); + +// 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 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(); +``` + +### ISO datetimes + +As you may have noticed, Zod string includes a few date/time related validations. These validations are regular expression based, so they are not as strict as a full date/time library. However, they are very convenient for validating user input. + +The `z.iso.datetime()` method enforces ISO 8601; by default, no timezone offsets are allowed: + +```ts +const datetime = z.iso.datetime(); + +datetime.parse("2020-01-01T00:00:00Z"); // ✅ +datetime.parse("2020-01-01T00:00:00.123Z"); // ✅ +datetime.parse("2020-01-01T00:00:00.123456Z"); // ✅ (arbitrary precision) +datetime.parse("2020-01-01T00:00:00+02:00"); // ❌ (no offsets allowed) +``` + +To allow Timezone offsets can be allowed by setting the `offset` option to `true`. + +```ts +const datetime = z.iso.datetime({ offset: true }); + +datetime.parse("2020-01-01T00:00:00+02:00"); // ✅ +datetime.parse("2020-01-01T00:00:00.123+02:00"); // ✅ (millis optional) +datetime.parse("2020-01-01T00:00:00.123+0200"); // ✅ (millis optional) +datetime.parse("2020-01-01T00:00:00.123+02"); // ✅ (only offset hours) +datetime.parse("2020-01-01T00:00:00Z"); // ✅ (Z still supported) +``` + +Allow unqualified (timezone-less) datetimes with the `local` flag. + +```ts +const schema = z.iso.datetime({ local: true }); +schema.parse("2020-01-01T00:00:00"); // ✅ +``` + +You can additionally constrain the allowable `precision`. By default, arbitrary sub-second precision is supported (but optional). + +```ts +const datetime = z.iso.datetime({ precision: 3 }); + +datetime.parse("2020-01-01T00:00:00.123Z"); // ✅ +datetime.parse("2020-01-01T00:00:00Z"); // ❌ +datetime.parse("2020-01-01T00:00:00.123456Z"); // ❌ +``` + +### 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 + +> Added in Zod 3.23 + +The `z.iso.time()` method validates strings in the format `HH:MM:SS[.s+]`. The second can include arbitrary decimal precision. It does not allow timezone offsets of any kind. + +```ts +const time = z.iso.time(); + +time.parse("00:00:00"); // ✅ +time.parse("09:52:31"); // ✅ +time.parse("23:59:59.9999999"); // ✅ (arbitrary precision) + +time.parse("00:00:00.123Z"); // ❌ (no `Z` allowed) +time.parse("00:00:00.123+02:00"); // ❌ (no offsets allowed) +``` + +You can set the `precision` option to constrain the allowable decimal precision. + +```ts +const time = z.iso.time({ precision: 3 }); + +time.parse("00:00:00.123"); // ✅ +time.parse("00:00:00.123456"); // ❌ +time.parse("00:00:00"); // ❌ +``` + +### IP addresses + +```ts +const ipv4 = z.ipv4(); +const ipv6 = z.ipv6(); +``` + +### IP ranges (CIDR) + +Validate IP address ranges specified with [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing). By default, `.cidr()` allows both IPv4 and IPv6. + +```ts +const cidr = z.string().cidr(); +cidr.parse("192.168.0.0/24"); // ✅ +cidr.parse("2001:db8::/32"); // ✅ +``` + +You can specify a version with the `version` parameter. + +```ts +const ipv4Cidr = z.string().cidr({ version: "v4" }); +ipv4Cidr.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // ❌ + +const ipv6Cidr = z.string().cidr({ version: "v6" }); +ipv6Cidr.parse("192.168.1.1"); // ❌ +``` + +## 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: + + + +```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(); +z.number().nonnegative(); +z.number().negative(); +z.number().nonpositive(); +z.number().multipleOf(5); // alias .step(5) +``` + + +```ts @zod/mini +z.number().check(z.gt(5)); +z.number().check(z.gte(5)); // alias .min(5) +z.number().check(z.lt(5)); +z.number().check(z.lte(5)); // alias .max(5) +z.number().check(z.positive()); +z.number().check(z.nonnegative()); +z.number().check(z.negative()); +z.number().check(z.nonpositive()); +z.number().check(z.multipleOf(5)); // alias .step(5) +``` + + + +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. + + + +```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(); +z.bigint().nonnegative(); +z.bigint().negative(); +z.bigint().nonpositive(); +z.bigint().multipleOf(5n); // alias `.step(5n)` +``` + + +```ts @zod/mini +z.bigint().check(z.gt(5n)); +z.bigint().check(z.gte(5n)); // alias `.min(5n)` +z.bigint().check(z.lt(5n)); +z.bigint().check(z.lte(5n)); // alias `.max(5n)` +z.bigint().check(z.positive()); +z.bigint().check(z.nonnegative()); +z.bigint().check(z.negative()); +z.bigint().check(z.nonpositive()); +z.bigint().check(z.multipleOf(5n)); // alias `.step(5n)` +``` + + + + +## 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-12T00:00: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. + + + +```ts +z.date().min(new Date("1900-01-01"), { error: "Too old!" }); +z.date().max(new Date(), { error: "Too young!" }); +``` + + +```ts @zod/mini +z.date().check(z.minimum(new Date("1900-01-01"), { error: "Too old!" })); +z.date().check(z.maximum(new Date(), { error: "Too young!" })); +``` + + + +
+ +## 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"); // => ❌ +``` + + + 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; // 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; // "Salmon" | "Tuna" | "Trout" + ``` + + +You can also pass in an externally-declared TypeScript enum. + + +**Zod 4** — This replaces the `z.nativeEnum()` API in Zod 3. + +Note that using TypeScript's `enum` keyword is [not recommended](https://www.totaltypescript.com/why-i-dont-like-typescript-enums). + + +```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: + + + +```ts +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); + +FishEnum.enum; +// => { Salmon: "Salmon", Tuna: "Tuna", Trout: "Trout" } +``` + + +```ts @zod/mini +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); + +FishEnum.def.entries; +// => { Salmon: "Salmon", Tuna: "Tuna", Trout: "Trout" } +``` + + + +### `.exclude()` + +To create a new enum schema, excluding certain values: + + + +```ts +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); +const SalmonAndTroutOnly = FishEnum.extract(["Salmon", "Trout"]); +``` + + +```ts @zod/mini +// no equivalent + +``` + + + +### `.extract()` + +To create a new enum schema, extracting certain values: + + + +```ts +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); +const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]); +``` + + +```ts @zod/mini +// no equivalent + +``` + + + +## Stringbool + +> **💎 New in Zod 4** + +In some cases (e.g. parsing environment variables) it's valuable to parse certain string "boolish" values to a plain `boolean` value. To support this, Zod 4 introduces `z.stringbool()`: + +```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("enable") // => 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"] +}) +``` + +Be default the schema is *case-insensitive*; all inputs are converted to lowercase before comparison to the `truthy`/`falsy` values. To make it case-sensitive: + +```ts +z.stringbool({ + case: "sensitive" +}); +``` + +## Optionals + +To make a schema *optional* (that is, to allow `undefined` inputs). + + + +```ts +z.optional(z.literal("yoda")); // or z.literal("yoda").optional() +``` + + +```ts @zod/mini +z.optional(z.literal("yoda")); +``` + + + +This returns a `ZodOptional` instance that wraps the original schema. To extract the inner schema: + + + +```ts +optionalYoda.unwrap(); // ZodLiteral<"yoda"> +``` + + +```ts @zod/mini +optionalYoda.def.innerType; // ZodLiteral<"yoda"> +``` + + + +## Nullables + +To make a schema *nullable* (that is, to allow `null` inputs). + + + +```ts +z.nullable(z.literal("yoda")); // or z.literal("yoda").nullable() +``` + + +```ts @zod/mini +const nullableYoda = z.nullable(z.literal("yoda")); +``` + + + +This returns a `ZodNullable` instance that wraps the original schema. To extract the inner schema: + + + +```ts +nullableYoda.unwrap(); // ZodLiteral<"yoda"> +``` + + +```ts @zod/mini +nullableYoda.def.innerType; // ZodLiteral<"yoda"> +``` + + + +## Nullish + +To make a schema *nullish* (both optional and nullable): + + + +```ts +const nullishYoda = z.nullish(z.literal("yoda")); +``` + + +```ts @zod/mini +const nullishYoda = z.nullish(z.literal("yoda")); +``` + + + +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` +``` + +## Never + +No value will pass validation. + +```ts +z.never(); // inferred type: `never` +``` + +## Template literals + + +> **💎 New in Zod 4** + +Zod 4 finally implements one of the last remaining unrepresented features of TypeScript's type system: template literals. Virtually all primitive schemas can be used in `z.templateLiteral`: strings, string formats like `z.email()`, numbers, booleans, enums, literals (of the non-template variety), optional/nullable, and other template literals. + +```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!) +``` + +## Objects + +There are two APIs for defining object schemas: `z.object()` and `z.interface()`. The two APIs are largely identical, with a small (but important!) difference in how *optional properties* are defined. +{/* + +The `z.interface()` API was introduced in Zod 4 as a more flexible and powerful way to represent objects. Optional properties are specified with a `?` prefix in the key. This gives you more control over representing *optionality*: + +```ts +z.interface({ + email: z.string(), + name: z.string().optional(), + "bio?": z.string(), +}); +// { email: string; name: string | undefined; bio?: string; } +``` + +With this new syntax, it's now possible to define recursive types directly using getters—no `z.lazy()` required. + +```ts +const Node = z.interface({ + value: z.string(), + get children(){ + return z.array(Node).optional() + } +}); +``` + +For these reasons, `z.interface` is the recommended approach. However, `z.object()` is still fully supported. + */} + +{/* For a detailed discussion of the differences, refer to the [introductory blog post](#) for `z.interface()`. */} + + + + +{/* The `z.interface()` API fixes some shortcomings with `z.object()`. Let's run through them. */} + +There are a couple ways in which a key can be "optional" in TypeScript. Consider these two inferfaces. + +```ts +inferface A { + name?: string | undefined; +} + +inferface B { + name: string | undefined; +} +``` + +In `A`, the `name` property can be set `undefined` or it can be omitted from the object entirely. By contrast, `B` requires that a `name` propertly is set. + +```ts +const obj: A = { name: undefined }; // ✅ +const obj: A = {} ; // ❌ compile error + +const obj: B = { name: undefined }; // ✅ +const obj: B = {} ; // ✅ pass +``` + + +The `z.object()` API can only represent `A`. When an an optional schema (e.g. `z.string().optional()`) is passed into `z.object()`, Zod automatically adds the question mark to the inferred type. + +```ts +z.object({ name: z.string().optional() }); +// { name?: string | undefined; } +``` + +By contrast, `z.inferface()` can represent both varieties. To make a property "key optional" (e.g. to add the question mark), prefix that key with `?`. + + +```ts +z.interface({ "name?": z.string() }); +// { name?: string | undefined; } + +z.interface({ name: z.string().optional() }); +// { name: string | undefined; } +``` + +As a happy side-effect of this new `z.interface()` API, it's possible to define recursive types directly—no type-casting or `z.lazy()` required! See the documentation on [`Recursive objects`](#recursive-objects) for more information. + + + +To define an object type: + + + + ```ts z.interface + // all properties are required by default + const Person = z.interface({ + name: z.string(), + age: z.number(), + }); + + type Person = z.infer; + // => { name: string; age: number; } + ``` + + + + ```ts z.object + // all properties are required by default + const Person = z.object({ + name: z.string(), + age: z.number(), + }); + + type Person = z.infer; + // => { name: string; age: number; } + ``` + + + +By default, all properties are required. To make certain properties optional: + + + +```ts z.interface +const Dog = z.interface({ + name: z.string(), + "age?": z.number(), // optional field +}); + +Dog.parse({ name: "Yeller" }); // ✅ +``` + + + +```ts z.object +const Dog = z.object({ + name: z.string(), + age: z.number().optional(), +}); + +Dog.parse({ name: "Yeller" }); // ✅ +``` + + + +By default, unrecognized keys are *stripped* from the parsed result: + + + +```ts z.interface +Dog.parse({ name: "Yeller", extraKey: true }); +// => { name: "Yeller" } +``` + + +```ts z.object +Dog.parse({ name: "Yeller", extraKey: true }); +// => { name: "Yeller" } +``` + + + +To define a *strict* schema that throws an error when unknown keys are found: + + + +```ts z.interface +const StrictDog = z.strictInterface({ + name: z.string(), +}); + +StrictDog.parse({ name: "Yeller", extraKey: true }); +// ❌ throws +``` + + +```ts z.object +const StrictDog = z.strictObject({ + name: z.string(), +}); + +StrictDog.parse({ name: "Yeller", extraKey: true }); +// ❌ throws +``` + + + +To define a *loose* schema that allows unknown keys to pass through: + + + +```ts z.interface +const LooseDog = z.looseInterface({ + name: z.string(), +}); + +Dog.parse({ name: "Yeller", extraKey: true }); +// => { name: "Yeller", extraKey: true } +``` + + +```ts z.object +const LooseDog = z.looseObject({ + name: z.string(), +}); + +Dog.parse({ name: "Yeller", extraKey: true }); +// => { name: "Yeller", extraKey: true } +``` + + + +{/* +To validate unknown keys against a schema: + + +```ts z.interface +const Dog = z.interface({ + name: z.string(), +}).catchall; + +Dog.parse({ name: "Yeller", extraKey: true }); +// => { name: "Yeller" } +``` +```ts z.object +const Dog = z.strictObject({ + name: z.string(), +}); + +Dog.parse({ name: "Yeller", extraKey: true }); +// => { name: "Yeller" } +``` + */} + +### `.shape` + +To access the internal schemas: + + + +```ts +Dog.shape.name; // => string schema +Dog.shape.age; // => number schema +``` + + +```ts @zod/mini +Dog.def.shape.name; // => string schema +Dog.def.shape.age; // => number schema +``` + + + +### `.keyof()` + +To create a `ZodEnum` schema from the keys of an object schema: + + + +```ts +const keySchema = Dog.keyof(); +// => ZodEnum<["name", "age"]> +``` + + +```ts @zod/mini +const keySchema = z.keyof(Dog); +// => ZodEnum<["name", "age"]> +``` + + + +### `.extend()` + +To add additional fields to an object schema: + + + +```ts +const DogWithBreed = Dog.extend({ + breed: z.string(), +}); +``` + + +```ts @zod/mini +const DogWithBreed = z.extend(Dog, { + breed: z.string(), +}); +``` + + + + + This API can be used to overwrite existing fields! Be careful with this power! + + +You can also pass another object schema into `.extend()`: + + + +```ts +const HasBreed = z.interface({ breed: z.string() }); +const DogWithBreed = Dog.extend(HasBreed); +``` + + +```ts @zod/mini +const HasBreed = z.interface({ breed: z.string() }); +const DogWithBreed = z.extend(Dog, HasBreed); +``` + + + + +If the two schemas share keys, B will override A. The returned schema also inherits the strictness level (strip, strict, loose) from B. + + +{/* +### `.merge()` + +```ts +const BaseTeacher = z.object({ students: z.array(z.string()) }); +const HasID = z.object({ id: z.string() }); + +const Teacher = BaseTeacher.merge(HasID); +type Teacher = z.infer; // => { students: string[], id: string } +``` + +> If the two schemas share keys, the properties of B overrides the property of A. The returned schema also inherits the "unknownKeys" policy (strip/strict/passthrough) and the catchall schema of B. */} + +### `.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.interface +const Recipe = z.interface({ + title: z.string(), + "description?": z.string(), + ingredients: z.array(z.string()), +}); +// { name: string; description?: string; ingredients: string[] } +``` + + +```ts z.object +const Recipe = z.object({ + name: z.string(), + description: z.string().optional(), + ingredients: z.array(z.string()), +}); +// { id: string; name: string; ingredients: string[] } +``` + + + +To pick certain keys: + + + + +```ts zod +const JustTheTitle = Recipe.pick({ title: true }); +``` + + +```ts @zod/mini +const JustTheTitle = z.pick(Recipe, { title: true }); +``` + + + + +### `.omit` + +To omit certain keys: + + + +```ts zod +const RecipeNoId = Recipe.omit({ id: true }); +``` + + +```ts @zod/mini +const RecipeNoId = z.omit(Recipe, { id: true }); +``` + + + + +### `.partial()` + +For convenience, Zod provides a dedicated API for make some or all properties optional, inspired by the built-in TypeScript utility type [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype). +{/* + + + +```ts z.interface +const User = z.object({ + email: z.string(), + username: z.string(), +}); +// { email: string; username: string } +``` + + +```ts z.object +const User = z.object({ + email: z.string(), + username: z.string(), +}); +// { email: string; username: string } +``` + + */} + +To make all fields optional: + + + +```ts zod +const PartialRecipe = Recipe.partial(); +// { title?: string | undefined; discription?: string | undefined; ingredients?: string[] | undefined } +``` + + +```ts @zod/mini +const PartialRecipe = z.partial(Recipe); +// { title?: string | undefined; discription?: string | undefined; ingredients?: string[] | undefined } +``` + + + +To make certain properties optional: + + + +```ts zod +const RecipeOptionalIngredients = Recipe.partial({ + ingredients: true, +}); +// { title: string; description?: string | undefined; ingredients?: string[] | undefined } +``` + + +```ts @zod/mini +const RecipeOptionalIngredients = z.partial(User, { + ingredients: true, +}); +// { title: string; description?: string | undefined; ingredients?: string[] | undefined } +``` + + + +### `.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. + +{/* +Starting from this schema: + + + +```ts z.interface +const User = z.object({ + "email?": z.string(), + "username?": z.string(), +}); +// { email?: string | undefined; username?: string | undefined } +``` + + +```ts z.object +const User = z.object({ + email: z.string().optional(), + username: z.string().optional(), +}); +// { email?: string | undefined; username?: string | undefined } +``` + + */} + +To make all properties required: + + + +```ts zod +const RequiredRecipe = Recipe.required(); +// { title: string; description: string; ingredients: string[] } +``` + + +```ts @zod/mini +const RequiredRecipe = z.required(Recipe); +// { title: string; description: string; ingredients: string[] } +``` + + + +To make certain properties required: + + + +```ts zod +const RecipeRequiredDescription = Recipe.required({description: true}); +// { title: string; description: string; ingredients: string[] } +``` + + +```ts @zod/mini +const RecipeRequiredDescription = z.required(Recipe, {description: true}); +// { title: string; description: string; ingredients: string[] } +``` + + + +{/* ### `.passthrough` + +By default Zod object schemas strip out unrecognized keys during parsing. + +```ts +const person = z.object({ + name: z.string(), +}); + +person.parse({ + name: "bob dylan", + extraKey: 61, +}); +// => { name: "bob dylan" } +// extraKey has been stripped +``` + +Instead, if you want to pass through unknown keys, use `.passthrough()` . + +```ts +person.passthrough().parse({ + name: "bob dylan", + extraKey: 61, +}); +// => { name: "bob dylan", extraKey: 61 } +``` + +### `.strict` + +By default Zod object schemas strip out unrecognized keys during parsing. You can _disallow_ unknown keys with `.strict()` . If there are any unknown keys in the input, Zod will throw an error. + +```ts +const person = z + .object({ + name: z.string(), + }) + .strict(); + +person.parse({ + name: "bob dylan", + extraKey: 61, +}); +// => throws ZodError +``` + +### `.strip` + +You can use the `.strip` method to reset an object schema to the default behavior (stripping unrecognized keys). + +### `.catchall` + +You can pass a "catchall" schema into an object schema. All unknown keys will be validated against it. + +```ts +const person = z + .object({ + name: z.string(), + }) + .catchall(z.number()); + +person.parse({ + name: "bob dylan", + validExtraKey: 61, // works fine +}); + +person.parse({ + name: "bob dylan", + validExtraKey: false, // fails +}); +// => throws ZodError +``` + +Using `.catchall()` obviates `.passthrough()` , `.strip()` , or `.strict()`. All keys are now considered "known". + +
*/} + +## Recursive objects + +The `z.interface()` API is capable of representing recursive types using [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get). + +```ts +const Category = z.interface({ + name: z.string(), + get subcategories(){ + return z.array(Category) + } +}); + +type Category = z.infer; +// { name: string; subcategories: Category[] } +``` + +You can also represent *mutually recursive types*: + +```ts +const User = z.interface({ + email: z.email(), + get posts(){ + return z.array(Post) + } +}); + +const Post = z.interface({ + title: z.string(), + get author(){ + return User + } +}); +``` + +All object APIs (pick, omit, required, partial, etc) work as you'd expect. + + + +Due to a limitation in TypeScript, `z.object()` isn't capable of defining recursive types without a manual type definition. This means you need to redundantly define a TypeScript type in addition to your Zod schema. + +```ts +interface Category { + name: string; + subcategories: Category[]; +}; + +const categorySchema: z.ZodType = z.object({ + name: z.string(), + subcategories: z.lazy(() => categorySchema.array()), +}); +``` + +This is verbose, error prone, and counter to the goals of Zod. The `z.interface()` API was introduced in large part so recursive types could be more readily represented in Zod. + + + +{/* ### Cyclical objects */} + + + Though recursive schemas are supported, passing cyclical data into Zod will cause an infinite loop. + + + + +> To detect cyclical objects before they cause problems, consider [this approach](https://gist.github.com/colinhacks/d35825e505e635df27cc950776c5500b). + +## Arrays + +To define an array schema: + + + +```ts +const stringArray = z.array(z.string()); // or z.string().array() +``` + + +```ts @zod/mini +const stringArray = z.array(z.string()); +``` + + + +To access the inner schema for an element of the array. + + + +```ts +stringArray.unwrap(); // => string schema +``` + + +```ts @zod/mini +stringArray.def.element; // => string schema +``` + + + +{/* ### `.nonempty` + +If you want to ensure that an array contains at least one element, use `.nonempty()`. + +```ts +const nonEmptyStrings = z.string().array().nonempty(); +// the inferred type is now +// [string, ...string[]] + +nonEmptyStrings.parse([]); // throws: "Array cannot be empty" +nonEmptyStrings.parse(["Ariana Grande"]); // passes +``` + +You can optionally specify a custom error message: + +```ts +// optional custom error message +const nonEmptyStrings = z.string().array().nonempty({ + message: "Can't be empty!", +}); +``` */} + +### `.min/.max/.length` + + + +```ts +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 +``` + + +```ts @zod/mini +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 +``` + + + +{/* 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; +// [string, number, boolean] +``` + +To add a variadic ("rest") argument: + +```ts +const variadicTuple = z.tuple([z.string()], z.number()); +// => [string, ...number[]]; +``` + +## 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: + + + +```ts +stringOrNumber.options; // [ZodString, ZodNumber] +``` + + +```ts @zod/mini +stringOrNumber.def.options; // [ZodString, ZodNumber] +``` + + + +{/* 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 +``` + +
*/} + +## 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 with 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 leverages the discriminator key to make parsing more efficient. + + +```ts +const MyResult = z.discriminatedUnion([ + z.interface({ status: z.literal("success"), data: z.string() }), + z.interface({ status: z.literal("failed"), error: z.string() }), +]); +``` + + + + In Zod 3, you were required to specify the discriminator key as the first argument. This is no longer necessary, as Zod can now automatically detect the discriminator key. + + ```ts + const MyResult = z.discriminatedUnion("status", [ + z.interface({ status: z.literal("success"), data: z.string() }), + z.interface({ status: z.literal("failed"), error: z.string() }), + ]); + ``` + + If Zod can't find a discriminator key, it will throw an error at schema creation time. + + + + + 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([ + z.interface({ ...BaseError, code: z.literal(400) }), + z.interface({ ...BaseError, code: z.literal(401) }), + z.interface({ ...BaseError, code: z.literal(500) }), + ]); + + const MyResult = z.discriminatedUnion([ + z.interface({ status: z.literal("success"), data: z.string() }), + MyErrors + ]); + ``` + + + +## 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; // => number +``` + +This can be useful for intersecting two object types. + +```ts +const Person = z.intersection({ name: z.string() }); +type Person = z.infer; + +const Employee = z.intersection({ role: z.string() }); +type Employee = z.infer; + +const EmployedPerson = z.intersection(Person, Employee); +type EmployedPerson = z.infer; +// Person & Employee +``` + + + In most cases, it is better to use [`A.extend(B)`](#extend) to merge two object schemas. This approach returns a new object schema, whereas `z.intersection(A, B)` returns a `ZodIntersection` instance which lacks common object methods like `pick` and `omit`. + + +## Records + +Record schemas are used to validate types such as `Record`. + +```ts +const IdCache = z.record(z.string(), z.string()); + +IdCache.parse({ + carlotta: "77d2586b-9e8e-4ecf-8b21-ea7e0530eadd", + jimmie: "77d2586b-9e8e-4ecf-8b21-ea7e0530eadd", +}); +``` + +The key schema can be any Zod schema that as assignable to `string | number | symbol`. + +```ts +const Keys = z.union([z.string(), z.number(), z.symbol()]); +const AnyObject = z.record(Keys, z.unknown()); +// Record +``` + +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()); +``` + + + +Despite the fact that TypeScript lets you define `Record` types with `number` keys (e.g. `Record`), numerical keys don't actually exist in JavaScript. + +```ts +const myObject = { 1: "one" }; + +Object.keys(myObject); +// => ["1"] +``` + +As you can see, JavaScript automatically casts all numeric keys to strings under the hood. As such, using `z.number()` as a key schema inside `z.record()` will always throw an error during parsing, but Zod allows it for the sake of parity with TypeScript's type system. + + + +## Maps + +```ts +const StringNumberMap = z.map(z.string(), z.number()); +type StringNumberMap = z.infer; // Map + +const myMap: StringNumberMap = new Map(); +myMap.set("one", 1); +myMap.set("two", 2); + +StringNumberMap.parse(myMap); +``` + +## Sets + +```ts +const NumberSet = z.set(z.number()); +type NumberSet = z.infer; // Set + +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. + + + +```ts +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 +``` + + +```ts +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 +``` + + + +## Promises + + + **Deprecated** — `z.promise()` is deprecated in Zod 4. There are vanishingly few valid uses cases for a `Promise` schema. If you suspect a value might be a `Promise`, simply `await` it before parsing it with Zod. + + + + +```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 + +const test = async () => { + await numberPromise.parse(Promise.resolve("tuna")); + // ZodError: Non-number type: string + + await numberPromise.parse(Promise.resolve(3.14)); + // => 3.14 +}; +``` + + + +## 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"); // ❌ +``` + +## Refinements + +Every Zod schema stores an array of *refinements*. Refinements are a way to perform additional custom validation that can Zod doesn't provide a native API for. + +### `.refine()` + +{/* + Checks do not (in fact, cannot) change the inferred type of the schema. + + +### `.refine()` */} + + + +```ts +const myString = z.string().refine((val) => val.length <= 255); +``` + + +```ts @zod/mini +const myString = z.string().check(z.refine((val) => val.length <= 255)); +``` + + + + + Refinement functions should never throw. Instead they should return a falsy value to signal failure. Thrown errors are not caught by Zod. + + +To customize the error message: + + + +```ts +const myString = z.string().refine((val) => val.length > 8, { + error: "Too short!" +}); +``` + + +```ts @zod/mini +const myString = z.string().check( + z.refine((val) => val.length > 8, { error: "Too short!" }) +); +``` + + + + +By default, validation issues from checks are considered *continuable*; that is, Zod will execute *all* checks in sequence, even if one of them causes an validation error. This is usually desirable, so Zod can present a comprehensive list of validation issues in one go. + + + +```ts +const myString = z.string() + .refine((val) => val.length > 8) + .refine((val) => val === val.toLowerCase()); + + +const result = myString.safeParse("OH NO"); +result.error.issues; +/* [ + { "code": "custom", "message": "Too short!" }, + { "code": "custom", "message": "Must be lowercase" } +] */ +``` + + +```ts @zod/mini +const myString = z.string().check( + z.refine((val) => val.length > 8), + z.refine((val) => val === val.toLowerCase()) +); + +const result = z.safeParse(myString, "OH NO"); +result.error.issues; +/* [ + { "code": "custom", "message": "Too short!" }, + { "code": "custom", "message": "Must be lowercase" } +] */ +``` + + + +To mark a particular refinement as *non-continuable*, use the `abort` parameter. Validation will terminate if the check fails. + + + +```ts +const myString = z.string() + .refine((val) => val.length > 8, { abort: true }) + .refine((val) => val === val.toLowerCase()); + + +const result = myString.safeParse("OH NO"); +result.error!.issues; +// [ { "code": "custom", "message": "Too short!" }] +``` + + +```ts @zod/mini +const myString = z.string().check( + z.refine((val) => val.length > 8, { abort: true }), + z.refine((val) => val === val.toLowerCase(), { abort: true }) +); + +const result = z.safeParse(myString, "OH NO"); +result.error!.issues; +// [ { "code": "custom", "message": "Too short!" }] +``` + + + +To customize the error path, use the `path` parameter. This is typically only useful in the context of object schemas. + + + +```ts +const passwordForm = z + .object({ + password: z.string(), + confirm: z.string(), + }) + .refine((data) => data.password === data.confirm, { + message: "Passwords don't match", + path: ["confirm"], // path of error + }); +``` + + +```ts +const passwordForm = z + .object({ + password: z.string(), + confirm: z.string(), + }) + .check(z.refine((data) => data.password === data.confirm, { + message: "Passwords don't match", + path: ["confirm"], // path of error + })); +``` + + + +This will set the `path` parameter in the associated issue: + + + +```ts +const result = passwordForm.safeParse({ password: "asdf", confirm: "qwer" }); +result.error.issues; +/* [{ + "code": "custom", + "path": [ "confirm" ], + "message": "Passwords don't match" +}] */ +``` + + +```ts +const result = z.safeParse(passwordForm, { password: "asdf", confirm: "qwer" }); +result.error.issues; +/* [{ + "code": "custom", + "path": [ "confirm" ], + "message": "Passwords don't match" +}] */ +``` + + + +Refinements can be `async`: + +```ts +const userId = z.string().refine(async (id) => { + // verify that ID exists in database + return true; +}); +``` + + + If you use async refinements, you must use the `.parseAsync` method to parse data! Otherwise Zod will throw an error. + + + + ```ts + const result = await userId.parseAsync("abc123"); + ``` + + + ```ts + const result = await z.parseAsync(userId, "abc123"); + ``` + + + + +### `.superRefine()` + + + In Zod 4, `.superRefine()` has been deprecated in favor of `.check()` + + + + + + + ```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, + }); + } + }); + ``` + + + ```ts + // no equivalent, use `z.check()` instead + + + + + + + + + + + + + + + + + + + + ``` + + + + + + +### `.check()` + + +The `.refine()` API is syntactic sugar atop a more versatile (and verbose) API called `.check()`. You can use this API to create multiple issues in a single refinement or have full control of the generated issue objects. + + + +```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, + continue: true // make this issue continuable (default: false) + }); + } +}); +``` + + +```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, + continue: true // make this issue continuable (default: false) + }); + } +}); +``` + + + +The regular `.refine` API only generated create issues with a `"custom"` error code, but `.superRefine()` makes it possible to throw other issue types. For more information on Zod's internal issue types, read the [Error customization]`(/error-customization) docs. + +## Pipes + +Schemas can be chained together into "pipes". Pipes are primarily useful when used in conjunction with [Transforms](#transforms). + + + +```ts +const stringToLength = z.string().pipe(z.transform(val => val.length)); + +stringToLength.parse("hello"); // => 5 +``` + + +```ts +const stringToLength = z.pipe(z.string(), z.transform(val => val.length)); + +z.parse(stringToLength, "hello"); // => 5 +``` + + + + +## Transforms + +Transforms are a special kind of schema. Instead of validating input, they accept anything and perform some transformation on the data. To define a transform: + + + +```ts +const castToString = z.transform((val) => String(val)); + +castToString.parse("asdf"); // => "asdf" +castToString.parse(123); // => "123" +castToString.parse(true); // => "true" +``` + + +```ts @zod/mini +const castToString = z.transform((val) => String(val)); + +z.parse(castToString, "asdf"); // => "asdf" +z.parse(castToString, 123); // => "123" +z.parse(castToString, true); // => "true" +``` + + + +{/* The output type of the schema is inferred from the transform function: + + + +```ts +const castToString = z.transform((val) => String(val)); + +type CastToString = z.infer; // string +``` + + +```ts @zod/mini +const castToString = z.transform((val) => String(val)); + +type CastToString = z.infer; // string +``` + + */} + + +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` + // return it to 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. + + + + +```ts +const stringToLength = z.string().pipe(z.transform(val => val.length)); + +stringToLength.parse("hello"); // => 5 +``` + + +```ts +const stringToLength = z.pipe(z.string(), z.transform(val => val.length)); + +z.parse(stringToLength, "hello"); // => 5 +``` + + + +### `.transform()` + +Piping some schema into a transform is a common pattern, so Zod provides a convenience `.transform()` method. + + + +```ts +const stringToLength = z.string().transform(val => val.length); +``` + + +```ts +// no equivalent +``` + + + +Transforms can also be async: + + + +```ts +const idToUser = z + .string() + .transform(async (id) => { + // fetch user from database + return db.getUserById(id); + }); + +const user = await idToUser.parseAsync("abc123"); +``` + + +```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"); +``` + + + + + If you use async transforms, you must use a `.parseAsync` or `.safeParseAsync` when parsing data! Otherwise Zod will throw an error. + + +### `.preprocess()` + +Piping a transform into another schema is another common pattern, so Zod provides a convenience `z.preprocess()` function. + +## Defaults + +To set a default value for a schema: + + + +```ts +const defaultTuna = z.string().default("tuna"); + +defaultTuna.parse(undefined); // => "tuna" +``` + + +```ts +const defaultTuna = z.default(z.string(), "tuna"); + +defaultTuna.parse(undefined); // => "tuna" +``` + + + + +{/* This value will be returned if the `input` is undefined. This is also true inside object schemas: + + + +```ts +const Fish = z.interface({ + name: z.string().default("tuna"), +}); + +Fish.parse({}); // => { name: "tuna" } +``` + + +```ts +const Fish = z.interface({ + name: z.string().default("tuna"), +}); + +z.parse(Fish, {}); // => { name: "tuna" } +``` + + */} + +Alternatively, you can pass a function which will be re-executed whenever a default value needs to be generated: + + + +```ts +const randomDefault = z.number().default(Math.random); + +randomDefault.parse(undefined); // => 0.4413456736055323 +randomDefault.parse(undefined); // => 0.1871840107401901 +randomDefault.parse(undefined); // => 0.7223408162401552 +``` + + +```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 +``` + + + +## Catch + +Use `.catch()` to define a fallback value to be returned in the event of a validation error: + + + +```ts +const numberWithCatch = z.number().catch(42); + +numberWithCatch.parse(5); // => 5 +numberWithCatch.parse("tuna"); // => 42 +``` + + +```ts +const numberWithCatch = z.catch(z.number(), 42); + +numberWithCatch.parse(5); // => 5 +numberWithCatch.parse("tuna"); // => 42 +``` + + + + +Alternatively, you can pass a function which will be re-executed whenever a catch value needs to be generated. + + + +```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 +``` + + +```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 +``` + + + +## 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 = fido; // works fine +``` + +In some cases, its 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; // { name: string } & z.$brand<"Cat"> +type Dog = z.infer; // { 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.infer; // { name: string } & z.$brand<"Cat"> +``` + +With this brand, any plain (unbranded) data structures are no longer assignable to the inferred type. You have to parse some data with the schema to get branded data. + + +> Note that branded types do not affect the runtime result of `.parse`. It is a static-only construct. + +## Readonly + +To mark a schema as readonly: + + + +```ts +const ReadonlyUser = z.object({ name: z.string() }).readonly(); +type ReadonlyUser = z.infer; +// Readonly<{ name: string }> +``` + + +```ts +const ReadonlyUser = z.readonly(z.object({ name: z.string() })); +type ReadonlyUser = z.infer; +// Readonly<{ name: string }> +``` + + + +This returns a new schema that wraps the original. The new schema's inferred type will be marked as `readonly`. Note that this only affects objects, arrays, tuples, `Set`, and `Map` in TypeScript: + + + +```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 +z.set(z.string()).readonly(); // ReadonlySet +``` + + +```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 +z.readonly(z.set(z.string())); // ReadonlySet +``` + + + +Inputs will be parsed using the original schema, 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. + + + +```ts +const result = ReadonlyUser.parse({ name: "fido" }); +result.name = "simba"; // throws TypeError +``` + + +```ts +const result = z.parse(ReadonlyUser, { name: "fido" }); +result.name = "simba"; // throws TypeError +``` + + + +## Template literals + +> **New in Zod 4** + +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` +``` + +## 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) + ]); +}); +``` + +## Custom + +You can create a Zod schema for any TypeScript type by using `z.custom()`. This is useful for creating schemas for types that are not supported by Zod out of the box, such as template string literals. + +```ts +const px = z.custom<`${number}px`>((val) => { + return typeof val === "string" ? /^\d+px$/.test(val) : false; +}); + +type px = z.infer; // `${number}px` + +px.parse("42px"); // "42px" +px.parse("42vw"); // 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"); +``` diff --git a/packages/docs/content/basics.mdx b/packages/docs/content/basics.mdx new file mode 100644 index 0000000000..f6a47f8614 --- /dev/null +++ b/packages/docs/content/basics.mdx @@ -0,0 +1,175 @@ +--- +title: Basic usage +--- + +import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; +import { Callout } from "fumadocs-ui/components/callout" + +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. + + + +```ts +import * as z from "zod"; + +const Player = z.interface({ + username: z. string(), + xp: z.number() +}); +``` + + +```ts +import * as z from "@zod/mini"; + +const Player = z.interface({ + username: z. string(), + xp: z.number() +}); +``` + + + +## Parsing data + +Now that we have a schema, we can parse some data with it. + +### `.parse()` + +`.parse(data: unknown): T` + +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 } +``` + +Otherwise, a `ZodError` instance is thrown with detailed information about the validation issues. + + + +```ts +try { + Player.parse({ username: 42, xp: "100" }); +} catch(err){ + if(error instanceof z.ZodError){ + err.issues; + /* [ + { + expected: 'string', + code: 'invalid_type', + path: [ 'username' ], + message: 'Invalid input: expected string' + }, + { + expected: 'number', + code: 'invalid_type', + path: [ 'xp' ], + message: 'Invalid input: expected number' + } + ] */ + } +} +``` + + +```ts +try { + Player.parse({ username: 42, xp: "100" }); +} catch(err){ + if(error instanceof z.core.$ZodError){ + err.issues; + /* [ + { + expected: 'string', + code: 'invalid_type', + path: [ 'username' ], + message: 'Invalid input: expected string' + }, + { + expected: 'number', + code: 'invalid_type', + path: [ 'xp' ], + message: 'Invalid input: expected number' + } + ] */ + } +} +``` + + + + +**Note** — If your schema uses certain asynchronous APIs like `async` [refinements](#refine) or [transforms](#transform), you'll need to use the `.safeParse()` method instead. + +```ts +const schema = z.string().refine(async (val) => val.length <= 8); + +await schema.parseAsync("hello"); +// => "hello" +``` + + +### `.safeParse()` + +`.safeParse(data:unknown): { success: true; data: T; } | { success: false; error: ZodError; }` + +To avoid `try/catch` blocks, use `.safeParse()`. This method returns an object containing either the successfully parsed data or a `ZodError`. + +```ts +Player.safeParse({ username: "billie", xp: 100 }); +// => { success: true; data: { username: "billie", xp: 100 } } + +Player.safeParse({ username: 42, xp: "100" }); +// => { success: false; error: 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 = stringSchema.safeParse("billie"); +if (!result.success) { + result.error; // handle error +} else { + result.data; // do stuff +} +``` + + +**Note** — If your schema uses certain asynchronous APIs like `async` [refinements](#refine) or [transforms](#transform), you'll need to use the `.safeParseAsync()` method instead. + +```ts +const schema = z.string().refine(async (val) => val.length <= 8); + +await schema.safeParseAsync("hello"); +// => { success: true; data: "hello" } +``` + + + + +## Inferred 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.interface({ + username: z. string(), + xp: z.number() +}); + +// extract the inferred type +type Player = z.infer; + +// use it in your code +const player: Player = { username: "billie", xp: 100 }; +``` + +--- + +Now that we have the basics covered, let's jump into the Schema API. diff --git a/packages/docs/content/ecosystem.mdx b/packages/docs/content/ecosystem.mdx new file mode 100644 index 0000000000..ead3253945 --- /dev/null +++ b/packages/docs/content/ecosystem.mdx @@ -0,0 +1,157 @@ +--- +title: Ecosystem +--- + +import { + ApiLibraries, + FormIntegrations, + ZodToX, + XToZod, + MockingLibraries, + PoweredByZod, + ZodUtilities, +} from "../components/ecosystem"; + +> **Note** — To avoid bloat and confusion, the Ecosystem section has been wiped clean with the release of Zod 4. If you've updated your library to work with Zod 4, please submit a PR to add it back in. + +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://twitter.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://twitter.com/mattpocockuk) +- [Fixing TypeScript's Blindspot: Runtime Typechecking](https://www.youtube.com/watch?v=rY_XqfSHock) by [@jherr](https://twitter.com/jherr) + + +## API Libraries + + + + +## Form Integrations + + + + +## Zod to X + + + + +## X to Zod + + + + +## Mocking Libraries + + + + +## Powered by Zod + + + + +## Zod Utilities + + + +{/* +## API libraries + +- [`tRPC`](https://github.com/trpc/trpc): Build end-to-end typesafe APIs without GraphQL. +- [`@anatine/zod-nestjs`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-nestjs): Helper methods for using Zod in a NestJS project. +- [`zod-endpoints`](https://github.com/flock-community/zod-endpoints): Contract-first strictly typed endpoints with Zod. OpenAPI compatible. +- [`zhttp`](https://github.com/evertdespiegeleer/zhttp): An OpenAPI compatible, strictly typed http library with Zod input and response validation. +- [`domain-functions`](https://github.com/SeasonedSoftware/domain-functions/): Decouple your business logic from your framework using composable functions. With first-class type inference from end to end powered by Zod schemas. +- [`@zodios/core`](https://github.com/ecyrbe/zodios): A typescript API client with runtime and compile time validation backed by axios and zod. +- [`express-zod-api`](https://github.com/RobinTail/express-zod-api): Build Express-based APIs with I/O schema validation and custom middlewares. +- [`tapiduck`](https://github.com/sumukhbarve/monoduck/blob/main/src/tapiduck/README.md): End-to-end typesafe JSON APIs with Zod and Express; a bit like tRPC, but simpler. +- [`koa-zod-router`](https://github.com/JakeFenley/koa-zod-router): Create typesafe routes in Koa with I/O validation using Zod. +- [`zod-sockets`](https://github.com/RobinTail/zod-sockets): Zod-powered Socket.IO microframework with I/O validation and built-in AsyncAPI specs +- [`oas-tszod-gen`](https://github.com/inkognitro/oas-tszod-gen): Client SDK code generator to convert OpenApi v3 specifications into TS endpoint caller functions with Zod types. +- [`GQLoom`](https://github.com/modevol-com/gqloom): Weave GraphQL schema and resolvers using Zod. + +## Form integrations + +- [`react-hook-form`](https://github.com/react-hook-form/resolvers#zod): A first-party Zod resolver for React Hook Form. +- [`zod-validation-error`](https://github.com/causaly/zod-validation-error): Generate user-friendly error messages from `ZodError`s. +- [`zod-formik-adapter`](https://github.com/robertLichtnow/zod-formik-adapter): A community-maintained Formik adapter for Zod. +- [`react-zorm`](https://github.com/esamattis/react-zorm): Standalone `` generation and validation for React using Zod. +- [`zodix`](https://github.com/rileytomasek/zodix): Zod utilities for FormData and URLSearchParams in Remix loaders and actions. +- [`conform`](https://conform.guide/api/zod/parseWithZod): A typesafe form validation library for progressive enhancement of HTML forms. Works with Remix and Next.js. +- [`remix-params-helper`](https://github.com/kiliman/remix-params-helper): Simplify integration of Zod with standard URLSearchParams and FormData for Remix apps. +- [`formik-validator-zod`](https://github.com/glazy/formik-validator-zod): Formik-compliant validator library that simplifies using Zod with Formik. +- [`zod-i18n-map`](https://github.com/aiji42/zod-i18n): Useful for translating Zod error messages. +- [`@modular-forms/solid`](https://github.com/fabian-hiller/modular-forms): Modular form library for SolidJS that supports Zod for validation. +- [`houseform`](https://github.com/crutchcorn/houseform/): A React form library that uses Zod for validation. +- [`sveltekit-superforms`](https://github.com/ciscoheat/sveltekit-superforms): Supercharged form library for SvelteKit with Zod validation. +- [`mobx-zod-form`](https://github.com/MonoidDev/mobx-zod-form): Data-first form builder based on MobX & Zod. +- [`@vee-validate/zod`](https://github.com/logaretm/vee-validate/tree/main/packages/zod): Form library for Vue.js with Zod schema validation. +- [`zod-form-renderer`](https://github.com/thepeaklab/zod-form-renderer): Auto-infer form fields from zod schema and render them with react-hook-form with E2E type safety. +- [`antd-zod`](https://github.com/MrBr/antd-zod): Zod adapter for Ant Design form fields validation. +- [`frrm`](https://github.com/schalkventer/frrm): Tiny 0.5kb Zod-based, HTML form abstraction that goes brr. + +## Zod to X + +- [`zod-to-ts`](https://github.com/sachinraja/zod-to-ts): Generate TypeScript definitions from Zod schemas. +- [`zod-to-json-schema`](https://github.com/StefanTerdell/zod-to-json-schema): Convert your Zod schemas into [JSON Schemas](https://json-schema.org/). +- [`@anatine/zod-openapi`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-openapi): Converts a Zod schema to an OpenAPI v3.x `SchemaObject`. +- [`zod-fast-check`](https://github.com/DavidTimms/zod-fast-check): Generate `fast-check` arbitraries from Zod schemas. +- [`zod-dto`](https://github.com/kbkk/abitia/tree/master/packages/zod-dto): Generate Nest.js DTOs from a Zod schema. +- [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod): Create Fastify type providers from Zod schemas. +- [`zod-to-openapi`](https://github.com/asteasolutions/zod-to-openapi): Generate full OpenAPI (Swagger) docs from Zod, including schemas, endpoints & parameters. +- [`nestjs-graphql-zod`](https://github.com/incetarik/nestjs-graphql-zod): Generates NestJS GraphQL model classes from Zod schemas. Provides GraphQL method decorators working with Zod schemas. +- [`zod-openapi`](https://github.com/samchungy/zod-openapi): Create full OpenAPI v3.x documentation from Zod schemas. +- [`fastify-zod-openapi`](https://github.com/samchungy/fastify-zod-openapi): Fastify type provider, validation, serialization and @fastify/swagger support for Zod schemas. +- [`typeschema`](https://typeschema.com/): Universal adapter for schema validation. +- [`zodex`](https://github.com/commonbaseapp/zodex): (De)serialization for zod schemas + +## X to Zod + +- [`ts-to-zod`](https://github.com/fabien0102/ts-to-zod): Convert TypeScript definitions into Zod schemas. +- [`@runtyping/zod`](https://github.com/johngeorgewright/runtyping): Generate Zod from static types & JSON schema. +- [`json-schema-to-zod`](https://github.com/StefanTerdell/json-schema-to-zod): Convert your [JSON Schemas](https://json-schema.org/) into Zod schemas. [Live demo](https://StefanTerdell.github.io/json-schema-to-zod-react/). +- [`json-to-zod`](https://github.com/rsinohara/json-to-zod): Convert JSON objects into Zod schemas. [Live demo](https://rsinohara.github.io/json-to-zod-react/). +- [`graphql-codegen-typescript-validation-schema`](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema): GraphQL Code Generator plugin to generate form validation schema from your GraphQL schema. +- [`zod-prisma`](https://github.com/CarterGrimmeisen/zod-prisma): Generate Zod schemas from your Prisma schema. +- [`Supervillain`](https://github.com/Southclaws/supervillain): Generate Zod schemas from your Go structs. +- [`prisma-zod-generator`](https://github.com/omar-dulaimi/prisma-zod-generator): Emit Zod schemas from your Prisma schema. +- [`drizzle-zod`](https://orm.drizzle.team/docs/zod): Emit Zod schemas from your Drizzle schema. +- [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod. +- [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models. +- [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas. +- [`@sanity-typed/zod`](https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/zod): Generate Zod Schemas from [Sanity Schemas](https://www.sanity.io/docs/schema-types). +- [`java-to-zod`](https://github.com/ivangreene/java-to-zod): Convert POJOs to Zod schemas +- [`Orval`](https://github.com/anymaniax/orval): Generate Zod schemas from OpenAPI schemas +- [`Kubb`](https://github.com/kubb-labs/kubb): Generate SDKs and Zod schemas from your OpenAPI schemas + +## Mocking + +- [`@anatine/zod-mock`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock): Generate mock data from a Zod schema. Powered by [faker.js](https://github.com/faker-js/faker). +- [`zod-mocking`](https://github.com/dipasqualew/zod-mocking): Generate mock data from your Zod schemas. +- [`zod-fixture`](https://github.com/timdeschryver/zod-fixture): Use your zod schemas to automate the generation of non-relevant test fixtures in a deterministic way. +- [`zocker`](https://zocker.sigrist.dev): Generate plausible mock-data from your schemas. +- [`zodock`](https://github.com/ItMaga/zodock) Generate mock data based on Zod schemas. +- [`zod-schema-faker`](https://github.com/soc221b/zod-schema-faker) Generates mock data from Zod schemas. Powered by [@faker-js/faker](https://github.com/faker-js/faker) and [randexp.js](https://github.com/fent/randexp.js) + +## Powered by Zod + +- [`freerstore`](https://github.com/JacobWeisenburger/freerstore): Firestore cost optimizer. +- [`slonik`](https://github.com/gajus/slonik/tree/gajus/add-zod-validation-backwards-compatible#runtime-validation-and-static-type-inference): Node.js Postgres client with strong Zod integration. +- [`schemql`](https://github.com/a2lix/schemql): Enhances your SQL workflow by combining raw SQL with targeted type safety and schema validation. +- [`soly`](https://github.com/mdbetancourt/soly): Create CLI applications with zod. +- [`pastel`](https://github.com/vadimdemedes/pastel): Create CLI applications with react, zod, and ink. +- [`zod-xlsx`](https://github.com/sidwebworks/zod-xlsx): A xlsx based resource validator using Zod schemas. +- [`znv`](https://github.com/lostfictions/znv): Type-safe environment parsing and validation for Node.js with Zod schemas. +- [`zod-config`](https://github.com/alexmarqs/zod-config): Load configurations across multiple sources with flexible adapters, ensuring type safety with Zod. +- [`unplugin-environment`](https://github.com/r17x/js/tree/main/packages/unplugin-environment#readme): A plugin for loading enviroment variables safely with schema validation, simple with virtual module, type-safe with intellisense, and better DX 🔥 🚀 👷. Powered by Zod. +- [`zod-struct`](https://codeberg.org/reesericci/zod-struct): Create runtime-checked structs with Zod. + +## Utilities for Zod + +- [`zod_utilz`](https://github.com/JacobWeisenburger/zod_utilz): Framework agnostic utilities for Zod. +- [`zod-playground`](https://github.com/marilari88/zod-playground): A tool for learning and testing Zod schema validation functionalities. [Link](https://zod-playground.vercel.app/). +- [`zod-sandbox`](https://github.com/nereumelo/zod-sandbox): Controlled environment for testing zod schemas. [Live demo](https://zod-sandbox.vercel.app/). +- [`zod-dev`](https://github.com/schalkventer/zod-dev): Conditionally disables Zod runtime parsing in production. +- [`zod-accelerator`](https://github.com/duplojs/duplojs-zod-accelerator): Accelerates Zod's throughput up to ~100x. */} diff --git a/packages/docs/content/error-customization.mdx b/packages/docs/content/error-customization.mdx new file mode 100644 index 0000000000..27ef92870b --- /dev/null +++ b/packages/docs/content/error-customization.mdx @@ -0,0 +1,341 @@ +--- +title: 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 `zod` package uses a subclass of this called `ZodError` that implements some additional convenience methods. + +Instances of `$ZodError` contain an `.issues` property containing a human-readable `message` and additional structured information about each encountered validation issue. + + + + +```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' +// } +// ] +``` + + +```ts +import * as z from "@zod/min"; + +const result = z.string().safeParse(12); // { success: false, error: z.core.$ZodError } +result.error.issues; +// [ +// { +// expected: 'string', +// code: 'invalid_type', +// path: [], +// message: 'Invalid input' +// } +// ] +``` + + + + +{/* ## Customization */} + +Every issue inside a `$ZodError` contains a `message` property with a human-readable error message. This message can be customized in a number of ways. + +{/* ## Error reporting in Zod + +Both Zod and `@zod/mini` depend on an common internal library called `@zod/core`. This core library exports an error class `$ZodError`, which serves as the basis for all error reporting in Zod. You can access this class using the `z.core` namespace. + +```ts +import * as z from "zod"; + +z.core.$ZodError; +``` + +The `zod` library exports a subclass of `$ZodError` called simply `ZodError`. This subclass adds some convenience methods to the base class. */} + + +## The `error` param + +Virtually every Zod API accepts an optional parameters object. The `error` param is used to customize the error messages produced by a particular schema. + + + +```ts +z.string({ error: "Bad!"}).parse(12); + +// throws ZodError { +// issues: [ +// { +// expected: 'string', +// code: 'invalid_type', +// path: [], +// message: 'Bad!' <-- 👀 custom error message +// } +// ] +// } +``` + + +```ts +z.string({ error: "Bad string"}).parse(12); + +// throws $ZodError { +// issues: [ +// { +// expected: 'string', +// code: 'invalid_type', +// path: [], +// message: 'Bad string' +// } +// ] +// } +``` + + + + +Nearly all functions and methods support `error`. + + + +```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!" }); +z.array(z.string(), { error: "Bad array!" }); +z.set(z.string(), { error: "Bad set!" }); +z.array(z.string(), { error: "Bad array!" }); +``` + + +```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!" }); +z.array(z.string(), { error: "Bad array!" }); +z.set(z.string(), { error: "Bad set!" }); +z.array(z.string(), { error: "Bad array!" }); +``` + + + + +The `error` param optionally accepts a function, also known as an **error map**. This function will be called at parse time. + +```ts +z.string({ error: ()=>`[${Date.now()}]: Validation failure.` }); +``` + + +**Note** — In Zod v3, there were separate params for `message` (a string) and `errorMap` (a function). These have been unified in Zod 4 as `error`. + + + +The `error` function received a context object you can use to customize the error message based on the `input` or other validation information. + + + +```ts +z.string({ + error: (iss) => iss.input===undefined ? "Field is required." : "Invalid input." +}); +``` + + +```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.path; // the path of the error + }, +}); +``` + + +```ts +z.string({ + error: (iss) => { + iss.inst; + iss.code; // the issue code + iss.input; // the input data + iss.inst; // the schema/check that originated this issue + iss.path; // the path of the error + }, +}); +``` + + + +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`; + }, +}); +``` + +## 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/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}`; + } + // ... + } +}) +``` + + +## 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/core`](/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}`; + } + // ... + } +}) +``` + +## Internationalization + +To support internationalization of error message, Zod provides several built-in **locales**. These are exported from the `@zod/core` package. + +> **Note** — The `zod` library automatically loads the `en` locale automatically. The `@zod/mini`package does not load any locale; instead all error messages default to `Invalid input`. + + + +```ts +import * as z from "zod"; +import en from "@zod/core/locales/en" + +z.config(en()); +``` + + +```ts +import * as z from "@zod/mini"; +import en from "@zod/core/locales/en" + +z.config(en()); +``` + + + +For convenience, you can also references these from the `z.core` namespace exports from `zod`/`@zod/mini`. + + + +```ts +import * as z from "zod"; + +z.config(z.core.locales.en()); +``` + + +```ts +import * as z from "@zod/mini"; + +z.config(z.core.locales.en()); +``` + + + + +### Locales + +The following locales are available: + +- `en` — English diff --git a/packages/docs/content/error-formatting.mdx b/packages/docs/content/error-formatting.mdx new file mode 100644 index 0000000000..6a48d1cd14 --- /dev/null +++ b/packages/docs/content/error-formatting.mdx @@ -0,0 +1,182 @@ +--- +title: 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 +const schema = z.object({ + username: z.string(), + favoriteNumbers: z.array(z.number()), +}); +``` + +Attempting to parse this invalid data results in an error containing two issues. + +```ts +const result = schema.safeParse({ + username: 1234, + favoriteNumbers: [1234, "4567"], +}); + +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 occured 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()` + + + This has been deprecated in favor of `z.treeifyError()`. + + + + + + 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 occured 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. + + + + +## `z.flattenError()` + + + This has been deprecated in favor of `z.treeifyError()`. + + + + + + While `z.formatError()` 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.string; // => [ 'Invalid input: expected string, received number' ] + flattened.fieldErrors.numbers; // => [ 'Invalid input: expected number, received string' ] + ``` + + > **Note** — If you have issues with a `path` longer than one level, this function throws away some error information. + + + diff --git a/packages/docs/content/generic-functions.mdx b/packages/docs/content/generic-functions.mdx new file mode 100644 index 0000000000..c0abcccf58 --- /dev/null +++ b/packages/docs/content/generic-functions.mdx @@ -0,0 +1,90 @@ +--- +title: Generic functions +--- + +### Writing generic functions + +With TypeScript generics, you can write reusable functions that accept Zod schemas as parameters. This enables you to create custom validation logic, schema transformations, and more, while maintaining type safety and inference. + +When attempting to write a function that accepts a Zod schema as an input, it's tempting to try something like this: + +```ts +function inferSchema(schema: z.ZodType) { + 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 +``` + +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`. + +A better approach is to infer _the schema as a whole_ instead of merely its inferred type. You can do this with a utility type called `z.ZodTypeAny`. + +```ts +function inferSchema(schema: T) { + return schema; +} + +inferSchema(z.string()); +// => ZodString +``` + +> `ZodTypeAny` is just a shorthand for `ZodType`, a type that is broad enough to match any Zod schema. + +The Result is now fully and properly typed, and the type system can infer the specific subclass of the schema. + +#### Inferring the inferred type + +If you follow the best practice of using `z.ZodTypeAny` as the generic parameter for your schema, you may encounter issues with the parsed data being typed as `any` instead of the inferred type of the schema. + +```ts +function parseData(data: unknown, schema: T) { + return schema.parse(data); +} + +parseData("sup", z.string()); +// => any +``` + +Due to how TypeScript inference works, it is treating `schema` like a `ZodTypeAny` instead of the inferred type. You can fix this with a type cast using `z.infer`. + +```ts +function parseData(data: unknown, schema: T) { + return schema.parse(data) as z.infer; + // ^^^^^^^^^^^^^^ <- add this +} + +parseData("sup", z.string()); +// => string +``` + +#### Constraining allowable inputs + +The `ZodType` class has three generic parameters. + +```ts +class ZodType< + Output = any, + Def extends ZodTypeDef = ZodTypeDef, + Input = Output +> { ... } +``` + +By constraining these in your generic input, you can limit what schemas are allowable as inputs to your function: + +```ts +function makeSchemaOptional>(schema: T) { + return schema.optional(); +} + +makeSchemaOptional(z.string()); +// works fine + +makeSchemaOptional(z.number()); +// Error: 'ZodNumber' is not assignable to parameter of type 'ZodType' +``` diff --git a/packages/docs/content/index.mdx b/packages/docs/content/index.mdx new file mode 100644 index 0000000000..cb86228680 --- /dev/null +++ b/packages/docs/content/index.mdx @@ -0,0 +1,183 @@ +--- +title: "Intro" +mode: "center" +--- + +import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; +import { Tabs } from 'fumadocs-ui/components/tabs'; + +import { Featured } from '../components/featured'; +import { Platinum } from '../components/platinum'; +import { Gold } from '../components/gold'; +import { Silver } from '../components/silver'; +import { Bronze } from '../components/bronze'; + + +
+
+ zod logo + zod logo +
+

Zod

+

TypeScript-first schema validation with static type inference

+ +
+ Website +   •   + Discord +   •   + 𝕏 +   •   + Bluesky +
+
+
+ +
+ +{/* + An example of a parameter field + + + + The filtering command used to sort through the users + + + + The age of the user. Cannot be less than 0 + */} + +
+ + Zod 4 is now in beta! Click here to read the release notes. + +
+ +


+ + + +## 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.interface({ + 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 + + + +## Requirements + + +Zod is tested against *TypeScript v5.5* and later. Older versions may work but are not officially supported. + +You must enable `strict` mode in your `tsconfig.json`. This is a best practice for all TypeScript projects. + + + ```ts + // tsconfig.json + { + // ... + "compilerOptions": { + // ... + "strict": true + } + } + ``` + +## Installation + +{/* ### `npm` */} + +```sh +npm install zod # npm +deno add npm:zod # deno +yarn add zod # yarn +bun add zod # bun +pnpm add zod # pnpm +``` + +Zod also publishes a canary version on every commit. To install the canary: + +```sh +npm install zod@canary # npm +deno add npm:zod@canary # deno +yarn add zod@canary # yarn +bun add zod@canary # bun +pnpm add zod@canary # pnpm +``` + +{/* > The rest of this README assumes you are using npm and importing directly from the `"zod"` package. */} + +## 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

+ + + + +
+ + +

Gold

+ + + + +
+ +

Silver

+ + + +
+ +

Bronze

+ + + diff --git a/packages/docs/content/json-schema.mdx b/packages/docs/content/json-schema.mdx new file mode 100644 index 0000000000..71d9b503cb --- /dev/null +++ b/packages/docs/content/json-schema.mdx @@ -0,0 +1,893 @@ +--- +title: "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'; + + + **New** — Zod 4 introduces a new feature: 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. + + + +To convert a Zod schema to JSON Schema, use the `z.toJSONSchema()` function. + +```ts +import * as z from "zod"; + +const schema = z.interface({ + name: z.string(), + age: z.number(), +}); + +z.toJSONSchema(schema) +// => { +// type: 'object', +// properties: { name: { type: 'string' }, age: { type: 'number' } }, +// required: [ 'name', 'age' ] +// } +``` + +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.void(); // ❌ +z.date(); // ❌ +z.map(); // ❌ +z.set(); // ❌ +z.file(); // ❌ +z.transform(); // ❌ +z.nan(); // ❌ +z.custom(); // ❌ +``` + + +## 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.time(); // => { type: "string", format: "time" } +z.iso.duration(); // => { type: "string", format: "duration" } +z.ipv4(); // => { type: "string", format: "ipv4" } +z.ipv6(); // => { type: "string", format: "ipv6" } +z.uuid(); // => { type: "string", format: "uuid" } +z.guid(); // => { type: "string", format: "uuid" } +z.url(); // => { type: "string", format: "uri" } +``` + +These schemas are supported via `contentEncoding`: + +```ts +z.base64(); // => { type: "string", contentEncoding: "base64" } +``` + +All other string formats are supported via `pattern`: + +```ts +z.cuid(); +z.regex(); +z.emoji(); +z.nanoid(); +z.cuid2(); +z.ulid(); +z.cidr(); +``` + +## 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: ... } +``` + +## Nullability + +Zod converts both `undefined`/`null` to `{ type: "null" }` in JSON Schema. + +```ts +z.null(); +// => { type: "null" } + +z.undefined(); +// => { type: "null" } +``` + +Similarly, `optional` and `nullable` are made nullable via `oneOf`: + +```ts +z.optional(z.string()); +// => { oneOf: [{ type: "string" }, { type: "null" }] } + +z.nullable(z.string()); +// => { oneOf: [{ type: "string" }, { type: "null" }] } +``` + +{/* ### Pipes + +Pipes contain and input and and output schema. Zod uses the *output schema* for JSON Schema conversion. */} + + +## Configuration + +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-7"` — Default. JSON Schema Draft 7 */ + target?: "draft-7" | "draft-2020-12"; + + /** A registry used to look up metadata for each schema. + * Any schema with an `id` property will be extracted as a $def. */ + metadata?: $ZodRegistry>; + + /** How to handle unrepresentable types. + * - `"throw"` — Default. Unrepresentable types throw an error + * - `"any"` — Unrepresentable types become `{}` */ + unrepresentable?: "throw" | "any"; + + /** 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; +} +``` + + +{/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParamDescriptionDefault
metadata + A registry used to look up metadata for each schema. All metadata will be copied into the resulting JSON Schema. + + Any schema with an `id` property will be extracted into `$defs`. + + `z.globalRegistry` +
target + The JSON Schema version to target. + + - `"draft-2020-12"` + - `"draft-7"` + + `"draft-2020-12"` +
unrepresentable + How to handle unrepresentable types. + + - `"throw"` — Unrepresentable types throw an error if encountered + - `"any"` — Unrepresentable types become `{}` + "throw"
cycles + How to handle cycles. + + - `"ref"` — Cycles will be broken using `$defs` + - `"throw"` — Cycles will throw an error if encountered + "ref"
reused + How to handle reused schemas. + + - `"inline"` — Reused schemas will be inlined + - `"ref"` — Reused schemas will be extracted into `$defs` + "inline"
external + A registry used to determine which schemas to convert to *external `$ref`s*. This is useful if you are generating individual `.json` files for some set of types. + + If defined, any schema with an id property in this registry is extracted as an external $ref. + `null`
externalURIA function used to convert id values to URIs to be used in external $refs.(id) => id
*/} + +### `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-7" }); +z.toJSONSchema(schema, { target: "draft-2020-12" }); +``` + +### `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`. + + + +```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", ... } +``` + + +```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", ... } +``` + + + + +### `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.void(); // ❌ +z.date(); // ❌ +z.map(); // ❌ +z.set(); // ❌ +z.file(); // ❌ +z.transform(); // ❌ +z.nan(); // ❌ +z.custom(); // ❌ +``` + +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 types to `{}` (the equivalent of `unknown` in JSON Schema). + +```ts +z.toJSONSchema(z.bigint(), { unrepresentable: "any" }); +// => {} +``` +### `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.interface({ + name: z.string(), + get friend() { + return User; + }, +}); + +toJSONSchema(User); +// => { +// type: 'object', +// properties: { name: { type: 'string' }, friend: { '$ref': '#' } }, +// required: [ 'name', 'friend' ] +// } +``` + +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.interface({ + firstName: name, + lastName: name, +}); + +z.toJSONSchema(User); +// => { +// type: 'object', +// properties: { +// firstName: { type: 'string' }, +// lastName: { type: 'string' } +// }, +// required: [ 'firstName', 'lastName' ] +// } +``` + +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' ], +// '$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 dircectly 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"; + } +}); +``` + + +### `pipes` + +Pipes contain an input and and output schema. By default the result of `z.toJSONSchema` represents the *output schema*; use `"pipe": "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, { pipe: "input" }); +// => { type: "string" } +``` + +{/* ### `uri` + +To customize all *external* `$ref` URIs, use the `uri` option. This expects a function that converts an `id` to a fully-qualified URI. + +```ts +const myRegistry = z.registry(); +const mySchema = z.string().meta({ id: "my-schema" }); +``` */} + + +{/* + + + 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.time(); // => { type: "string", format: "time" } + z.iso.duration(); // => { type: "string", format: "duration" } + z.ipv4(); // => { type: "string", format: "ipv4" } + z.ipv6(); // => { type: "string", format: "ipv6" } + z.uuid(); // => { type: "string", format: "uuid" } + z.guid(); // => { type: "string", format: "uuid" } + z.url(); // => { type: "string", format: "uri" } + ``` + + These schemas are supported via `contentEncoding`: + + ```ts + z.base64(); // => { type: "string", contentEncoding: "base64" } + ``` + + All other string formats are supported via `pattern`: + + ```ts + z.cuid(); + z.regex(); + z.emoji(); + z.nanoid(); + z.cuid2(); + z.ulid(); + z.cidr(); + ``` + + The following aren't supported, as they can't be accurately represented in JSON Schema: + + ```ts + z.jwt(); + z.ipv6(); + ``` + + + + Zod converts the following numeric types to JSON Schema: + + ```ts + // Supported via `type` + z.number(); // => { type: "number" } + + // Sets exclusiveMinimum, exclusiveMaximum + z.float32(); + z.float64(); + ``` + + ```ts + // Supported via type + z.int(); // => { type: "integer" } + + // Sets exclusiveMinimum, exclusiveMaximum + z.int32(); // => { type: "integer" } + ``` + + The following aren't supported, as they can't be accurately represented in JSON Schema: + + ```ts + z.bigint(); + z.int64(); + ``` + + + + Zod converts both `undefined`/`optional` and `null`/`nullable` to `null` in JSON Schema. + + ```ts + z.null(); + // => { type: "null" } + + z.undefined(); + // => { type: "null" } + + z.optional(z.string()); + // => { oneOf: [{ type: "string" }, { type: "null" }] } + + z.nullable(z.string()); + // => { oneOf: [{ type: "string" }, { type: "null" }] } + ``` + + + Pipes contain and input and and output schema. Zod uses the *output schema* for JSON Schema conversion. + + + */} + +{/* ### Primitives + +The following primitive types are converted into their JSON Schema equivalents: + +```ts +z.string(); // => { type: "string" } +z.number(); // => { type: "number" } +z.boolean(); // => { type: "boolean" } +z.null(); // => { type: "null" } +z.undefined(); // => { type: "null" } +z.literal("hello"); // => { enum: ["hello"] } +``` */} + +{/* +### String formats + +The following string formats are supported: + +```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.time(); // => { type: "string", format: "time" } +z.iso.duration(); // => { type: "string", format: "duration" } +z.ipv4(); // => { type: "string", format: "ipv4" } +z.ipv6(); // => { type: "string", format: "ipv6" } +z.uuid(); // => { type: "string", format: "uuid" } +z.guid(); // => { type: "string", format: "uuid" } +z.url(); // => { type: "string", format: "uri" } + +// Supported via `contentEncoding` +z.base64(); // => { type: "string", contentEncoding: "base64" } + +// Supported via `pattern` +z.cuid(); +z.regex(); +z.emoji(); +z.nanoid(); +z.cuid2(); +z.ulid(); +z.cidr(); +``` */} +{/* +### Numeric types + +The following `number` types are supported: + +```ts +// Supported via `type` +z.number(); // => { type: "number" } + +// Sets exclusiveMinimum, exclusiveMaximum +z.float32(); +z.float64(); +``` + +The following `integer` types are supported: + +```ts +// Supported via type +z.int(); // => { type: "integer" } + +// Sets exclusiveMinimum, exclusiveMaximum +z.int32(); // => { type: "integer" } +``` */} + +{/* +### Nullables + +Both `undefined`/`optional` and `null`/`nullable` are converted to `null` in JSON Schema. + +```ts +z.null(); // => { type: "null" } +z.undefined(); // => { type: "null" } + +z.optional(z.string()); +// => { oneOf: [{ type: "string" }, { type: "null" }] } + +z.nullable(z.string()); +// => { oneOf: [{ type: "string" }, { type: "null" }] } +``` */} + +{/* ### Arrays and tuples + +Full support for arrays and tuples: + +```ts +z.array(z.string()); +// => { type: "array", items: { type: "string" } } + +z.array(z.string()).min(1).max(10); +// => { type: "array", items: { type: "string" }, minItems: 1, maxItems: 10 } + +// Supported via `type` and `prefixItems` +z.tuple([ z.string(), z.number( ]); +// => { type: "array", prefixItems: [{ type: "string" }, { type: "number" }] } + +z.tuple([z.string()]).rest(z.boolean()); +// => { type: "array", prefixItems: [{ type: "string" }], additionalItems: { type: "boolean" } } +``` + +### Objects + +Full support for objects: + +```ts +z.interface({ name: z.string() }); +// => { type: "object", properties: { name: { type: "string" }}, required: ["name"] } + +z.strictInterface({ name: z.string() }) +// => { ..., additionalProperties: {not: {}} } + +z.looseInterface({ name: z.string() }) +// => { ..., additionalProperties: {} } + +z.interface({ "name?": z.string() }); +// => { type: "object", properties: { name: { type: "string" }}, required: [] } + +z.record(z.string(), z.string()); +// => { type: "object", propertyNames: { type: "string" }, additionalProperties: { type: "string" } } +``` */} + +{/* ### Special types + +```ts +z.any(); // => {} +z.unknown(); // => {} +z.never(); // => { not: {} } +``` */} + + +{/* ## Compare to `zod-to-json-schema` + +There is a popular existing third-party library for `zod-to-json-schema` available. It aims to provide a more flexible and configurable way to convert to JSON Schema, whereas `z.toJSONSchema()` is much simpler but less forgiving. For those who are currently using `zod-to-json-schema` here is a table of its supported options and how they are handled by Zod's built-in converter. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
name + Must be specified as an id property in a registry. +
nameStrategy + The `title` is an unrelated metadata property. Only `id` is used for refs. +
basePath + ✅ Supported +
$refStrategy + Only schemas specified in a registry will be pulled out as a ref. +
effectStrategy + The `ZodEffects` class no longer exists. Refinements are stored internally by schemas; they are supported. Transforms are represented with `ZodTransform` and are not supported. +
dateStrategy + The `z.date()` API is not supported. Use `z.iso.date()` to represent string-encoded dates. +
emailStrategy + This always uses `format: "email"`. If you specify a custom `pattern`, it will be added to the schema with `pattern`. +
base64Strategy + This always uses `contentEncoding: "base64"`. If your JSON Schema parser doesn't support `contentEncoding`, don't use `z.string().regex()` instead of `z.base64()`. +
definitionPath + ✅ Supported +
target + Out of scope +
strictUnions + ❌ Should never be necessary +
definitions + Registries make this obsolete. +
errorMessages + ❌ Not supported +
markdownDescription + ❌ Not supported +
patternStrategy + ❌ Not supported +
applyRegexFlags + Support is planned. +
pipeStrategy + Always uses the output schema. +
+ removeAdditionalStrategy +
+ allowedAdditionalProperties +
+ rejectedAdditionalProperties +
+ Additional properties is left unset by default. Use a `strict` schema if you want to disallow additional properties. +
override + Out of scope +
postProcess + Out of scope +
*/} + +## 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. To achieve this, you can pass a [registry](/metadata#registries) into `z.toJSONSchema()`. + +```ts +import * as z from "zod"; + +const User = z.interface({ + name: z.string(), + get posts(){ + return z.array(Post); + } +}); + +const Post = z.interface({ + title: z.string(), + content: z.string(), + get author(){ + return User; + } +}); + +z.globalRegistry.add(User, {id: "User"}); +z.globalRegistry.add(Post, {id: "Post"}); +``` + +The schemas above both have an `id` property registered in `z.globalRegistry`. To convert these to JSON Schema, pass the entire registry into `z.toJSONSchema()`. + +```ts +z.toJSONSchema(z.globalRegistry); +// => { +// schemas: { +// User: { +// id: 'User', +// type: 'object', +// properties: { +// name: { type: 'string' }, +// posts: { type: 'array', items: { '$ref': 'Post' } } +// }, +// required: [ 'name', 'posts' ] +// }, +// Post: { +// id: 'Post', +// type: 'object', +// properties: { +// title: { type: 'string' }, +// content: { type: 'string' }, +// author: { '$ref': 'User' } +// }, +// required: [ 'title', 'content', 'author' ] +// } +// } +// } +``` + +> Any schema with an `id` property in the registry will be extracted into `schemas`. + +By default the `$ref` URIs are 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' ] +// }, +// Post: { +// id: 'Post', +// type: 'object', +// properties: { +// title: { type: 'string' }, +// content: { type: 'string' }, +// author: { '$ref': 'https://example.com/User.json' } +// }, +// required: [ 'title', 'content', 'author' ] +// } +// } +// } +``` diff --git a/packages/docs/content/meta.json b/packages/docs/content/meta.json new file mode 100644 index 0000000000..31f30f1130 --- /dev/null +++ b/packages/docs/content/meta.json @@ -0,0 +1,22 @@ +{ + "root": true, + "pages": [ + "---Beta---", + "v4/index", + "v4/changelog", + "---Documentation---", + "index", + "basics", + "api", + "error-customization", + "error-formatting", + "metadata", + "json-schema", + "ecosystem", + "---Packages---", + "packages/zod", + "packages/mini", + "packages/json", + "packages/core" + ] +} diff --git a/packages/docs/content/metadata.mdx b/packages/docs/content/metadata.mdx new file mode 100644 index 0000000000..ba6bb0a2a3 --- /dev/null +++ b/packages/docs/content/metadata.mdx @@ -0,0 +1,206 @@ +--- +title: "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); +``` + +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 }); // ❌ +``` + +### `.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" }), +}) +``` + + +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()); +``` + + + +## 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; + examples?: T[]; // T is the output type of the schema you are registering + [k: string]: unknown; // accepts other properties too +} +``` + +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"] +}); +``` + +### `.meta()` + +For a more convenient approach, use the `.meta()` method to register a schema in `z.globalRegistry`. + + + +```ts +const emailSchema = z.email().meta({ + id: "email_address", + title: "Email address", + description: "Please enter a valid email address", +}); +``` + + +```ts +// no equivalent + + +``` + + + +Calling `.meta()` without an argument will *retrieve* the metadata for a schema. + + +```ts +emailSchema.meta(); +// => { id: "email_address", title: "Email address", ... } +``` + + +### `.describe()` + + +The `.describe()` method still exists for compatibility with Zod 3, but `.meta()` is now the recommended approach. + + +The `.describe()` method is a shorthand for registering a schema in `z.globalRegistry` with just a `description` field. + + + + +```ts +const emailSchema = z.email(); +emailSchema.describe("An email address"); + +// equivalent to +emailSchema.meta({ description: "An email address" }); +``` + + +```ts +// no equivalent + + + + +``` + + + +## 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(); + +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`). 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' +``` diff --git a/packages/docs/content/object-vs-interface.mdx b/packages/docs/content/object-vs-interface.mdx new file mode 100644 index 0000000000..86006817fc --- /dev/null +++ b/packages/docs/content/object-vs-interface.mdx @@ -0,0 +1,5 @@ +--- +title: "Object vs. interface" +--- + + diff --git a/packages/docs/content/packages/core.mdx b/packages/docs/content/packages/core.mdx new file mode 100644 index 0000000000..098e8b0fcb --- /dev/null +++ b/packages/docs/content/packages/core.mdx @@ -0,0 +1,432 @@ +--- +title: "@zod/core" +--- + +This page is primarily intended for consumption by *library authors* who are building tooling on top of Zod. + +## What's in `@zod/core` + +This 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 fro "@zod/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(...); +``` + +For simplicity, the `zod` and `@zod/mini` packages re-export their `@zod/core` dependency as `z.core`, but you shouldn't rely on this. + +```ts +import * as z from "zod"; + +z.core.$ZodString; +// the base class for string schemas +``` + +## Versioning + +While Zod 4 is in beta, the [`@zod/core`](https://www.npmjs.com/package/@zod/core) package is published as `latest` in the `v0.x` range ("initial development" according to semver). Once the first stable release release of Zod 4, at which point `v1.0.0` will be released. + +## Standard Schema + +If you're building a library that accepts user-defined Zod schemas (say, an API framework) you first first look into [Standard Schema](https://standardschema.dev/). Zod implements the Standard Schema spec, which is a library-agnostic "standard interface" for schema libraries to expose a validation method and their inferred input/output types. + +## Dependencies + +If you are building Zod-specific functionality (say, introspecting the schema) then Standard Schema won't be sufficient. In this case, you'll need a *peer dependency* on Zod. + +I recommend the following versioning strategy for your library: + +```ts +// package.json +{ + // ... + "peerDependencies": { + "@zod/core": "^0.1.0" + }, + "devDependencies": { + "@zod/core": "^0.1.0" // the oldest version you support + } +} +``` + +The peer dependency lets users "bring-their-own-Zod". As written this allows the user to use any `0.x.y` version of `@zod/core`. + +While you develop your library, you'll need to pick *some* version of Zod to use, in order to write your code. Use a *dev dependency* for this. This dev dependency ensures you fulfil the peer dependency requirement. + + +## Schemas + +The base class for all Zod schemas is `$ZodType`. It accepts two generic parameters: `Output` and `Input`. + +```ts +export class $ZodType { + _zod: { /* internals */} +} +``` + +`@zod/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 + | $ZodInterface + | $ZodUnion + | $ZodIntersection + | $ZodTuple + | $ZodRecord + | $ZodMap + | $ZodSet + | $ZodLiteral + | $ZodEnum + | $ZodPromise + | $ZodLazy + | $ZodOptional + | $ZodDefault + | $ZodTemplateLiteral + | $ZodCustom + | $ZodTransform + | $ZodNonOptional + | $ZodReadonly + | $ZodNaN + | $ZodPipe + | $ZodSuccess + | $ZodCatch + | $ZodFile; +``` + +All `@zod/core` subclasses only contain a single property: `_zod`. This property is an object containing the schemas *internals*. The goal is to make `@zod/core` as extensible and unopinionated as possible. Other libraries can "build their own Zod" on top of these classes without `@zod/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 + | $ZodBase64 + | $ZodE164 + | $ZodJWT +``` + + +## 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 { + _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_lenth"`, `"less_than"`, `"string_format"`, etc. +- `.check()` — Contains the check's validation logic. + +`@zod/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 + | $ZodBase64 + | $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` package directly uses `$ZodError` + +```ts +export class $ZodError 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/v4/packages/core/src/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/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/core`, you can use the following. This allows your users to bring any version of `@zod/core`, avoiding accidental duplicate installs. + + +```json +{ + "peerDependencies": { + "@zod/core": "*" + } +} +``` + +Since package managers generally won't install your own `peerDependencies`, you'll need to add `@zod/core` to your `devDependencies` as well. As new versions of `@zod/core` are released, you can update your `devDependencies` to match the latest version. This is important for testing and development purposes. + +```json +{ + "peerDependencies": { + "@zod/core": "*" + }, + "devDependencies": { + "@zod/core": "^0.1.0" + } +} +``` */} + +## Future proofing + +To future proof your library, you should always allow for new schema and check classes to be added in the future. If you are using switch statements to discriminate over union types, consider printing a warning when an unknown schema type is encountered. + +```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 +} +``` + +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. + +> If you are a schema author and think this page should include some additional guidance, please open an issue! diff --git a/packages/docs/content/packages/mini.mdx b/packages/docs/content/packages/mini.mdx new file mode 100644 index 0000000000..6ee9729ccd --- /dev/null +++ b/packages/docs/content/packages/mini.mdx @@ -0,0 +1,153 @@ +--- +title: "@zod/mini" +--- + +import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; + +The `@zod/mini` library was introduced with the release of Zod 4. It 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 +import * as z from "@zod/mini"; + +const mySchema = z.nullable(z.optional(z.string())); +``` + + +```ts +import * as z from "@zod/mini"; + +const mySchema = z.string().optional().nullable(); +``` + + + +> **Tree-shaking** — In `zod`, schemas provide a range of convenience methods to perform some common operations (e.g. `.min()` on string schemas). It isn't possible for bundlers to [tree-shake](https://en.wikipedia.org/wiki/Tree_shaking) these methods; that is, bundlers have a hard time removing method implementations from your bundle, even if they aren't used anywhere. By contrast, it's easy for bundlers to remove an unused top-level function. This is why `@zod/mini` relies so heavilty on functions. + +## `ZodMiniType` + +All `@zod/mini` schemas extend the `z.ZodMiniType` base class, which in turn extends `z.core.$ZodType` from [`@zod/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 `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); + +// mutations (these do not change the inferred types) +z.overwrite(value => newValue); +z.normalize(); +z.trim(); +z.toLowerCase(); +z.toUpperCase(); +``` + +### `.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 `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"; + +z.config(z.core.locales.en()); +``` + +Refer to the [Locales](/error-customization#internationalization) docs for more on localization. diff --git a/packages/docs/content/packages/zod.mdx b/packages/docs/content/packages/zod.mdx new file mode 100644 index 0000000000..e698adddbe --- /dev/null +++ b/packages/docs/content/packages/zod.mdx @@ -0,0 +1,74 @@ +--- +title: "zod" +--- + +The `zod` 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.interface({ + name: z.string(), + age: z.number().int().positive(), + email: z.string().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/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.and(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(); +mySchema.isOptional(); // boolean +mySchema.isNullable(); // boolean +``` diff --git a/packages/docs/content/parsing.mdx b/packages/docs/content/parsing.mdx new file mode 100644 index 0000000000..e53fc37f43 --- /dev/null +++ b/packages/docs/content/parsing.mdx @@ -0,0 +1,76 @@ +--- +title: Parsing data +--- + +Given a Zod schema, there are a number of ways to parse data with it. + +## `.parse` + +`.parse(data: unknown): T` + +Given any Zod schema, you can call its `.parse` method to check `data` is valid. If it is, a value is returned with full type information! Otherwise, an error is thrown. + +> IMPORTANT: The value returned by `.parse` is a _deep clone_ of the variable you passed in. + +```ts +const stringSchema = z.string(); + +stringSchema.parse("fish"); // => returns "fish" +stringSchema.parse(12); // throws error +``` + +## `.parseAsync` + +`.parseAsync(data:unknown): Promise` + +If you use asynchronous [refinements](#refine) or [transforms](#transform) (more on those later), you'll need to use `.parseAsync`. + +```ts +const stringSchema = z.string().refine(async (val) => val.length <= 8); + +await stringSchema.parseAsync("hello"); // => returns "hello" +await stringSchema.parseAsync("hello world"); // => throws error +``` + +## `.safeParse` + +`.safeParse(data:unknown): { success: true; data: T; } | { success: false; error: ZodError; }` + +If you don't want Zod to throw errors when validation fails, use `.safeParse`. This method returns an object containing either the successfully parsed data or a ZodError instance containing detailed information about the validation problems. + +```ts +stringSchema.safeParse(12); +// => { success: false; error: ZodError } + +stringSchema.safeParse("billie"); +// => { success: true; data: 'billie' } +``` + +The result is a _discriminated union_, so you can handle errors very conveniently: + +```ts +const result = stringSchema.safeParse("billie"); +if (!result.success) { + // handle error then return + result.error; +} else { + // do something + result.data; +} +``` + +## `.safeParseAsync` + +> Alias: `.spa` + +An asynchronous version of `safeParse`. + +```ts +await stringSchema.safeParseAsync("billie"); +``` + +For convenience, this has been aliased to `.spa`: + +```ts +await stringSchema.spa("billie"); +``` diff --git a/packages/docs/content/test.mdx b/packages/docs/content/test.mdx new file mode 100644 index 0000000000..910fda9249 --- /dev/null +++ b/packages/docs/content/test.mdx @@ -0,0 +1,17 @@ +--- +title: JSON +description: Components +--- + +## Code Block + +```js +console.log('Hello World'); +``` + +## Cards + + + + + diff --git a/packages/docs/content/type-inference.mdx b/packages/docs/content/type-inference.mdx new file mode 100644 index 0000000000..812a6f5042 --- /dev/null +++ b/packages/docs/content/type-inference.mdx @@ -0,0 +1,30 @@ +--- +title: Type inference +--- + +You can extract the TypeScript type of any schema with `z.infer` . + +```ts +const A = z.string(); +type A = z.infer; // string + +const u: A = 12; // TypeError +const u: A = "asdf"; // compiles +``` + +**What about transforms?** + +In reality each Zod schema internally tracks **two** types: an input and an output. For most schemas (e.g. `z.string()`) these two are the same. But once you add transforms into the mix, these two values can diverge. For instance `z.string().transform(val => val.length)` has an input of `string` and an output of `number`. + +You can separately extract the input and output types like so: + +```ts +const stringToNumber = z.string().transform((val) => val.length); + +// ⚠️ Important: z.infer returns the OUTPUT type! +type input = z.input; // string +type output = z.output; // number + +// equivalent to z.output! +type inferred = z.infer; // number +``` diff --git a/packages/docs/content/v4/changelog.mdx b/packages/docs/content/v4/changelog.mdx new file mode 100644 index 0000000000..a3f2cd35b4 --- /dev/null +++ b/packages/docs/content/v4/changelog.mdx @@ -0,0 +1,728 @@ +--- +title: Migration guide +--- + +import { Callout } from "fumadocs-ui/components/callout"; +import { Tabs, Tab } from "fumadocs-ui/components/tabs"; + +> To learn more about the performance enhancements and new features of Zod 4, read the [introductory post](/v4). + +This migration guide aims to list the breaking changes in Zod 4 in order of highest to lowest impact. Every effort was made to prevent breaking changes, but some are unavoidable. + + +**Note** — Zod 3 exported a large number of undocumented utility types and functions that are not considered part of the public API. Changes to those are not documented here, as they aren't considered part of Zod's public API. + + + +To install the beta: + +``` +npm upgrade zod@next +``` + +## Error customization + +This is perhaps the most visible of the removed APIs. Zod 4 standardizes the APIs for error customization under a single, unified `error` param. + +### deprecates `message` + +Replace `message` with `error`. The `message` parameter is still supported but deprecated. + + + +```ts +z.string().min(5, { error: "Too short." }); +``` + + +```ts +z.string().min(5, { message: "Too short." }); +``` + + + +### 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. + + + +```ts +z.string({ + error: (issue) => issue.input === undefined + ? "This field is required" + : "Not a string" +}); +``` + + +```ts +z.string({ + required_error: "This field is required", + invalid_type_error: "Not a string", +}); +``` + + + +### drops `errorMap` + +This is renamed to `error`. Error maps can also now return a string or `undefined` (which yields control to the next error map in the chain). + + + +```ts +z.string({ + error: (issue) => { + if (issue.code === "too_small") { + return `Value must be >${issue.minimum}` + } + }, +}); +``` + + +```ts +z.string({ + errorMap: (issue, ctx) => { + if (issue.code === "too_small") { + return { message: `Value must be >${issue.minimum}` }; + } + return { message: ctx.defaultError }; + }, +}); +``` + + + +## `ZodError` + +### no longer extends `Error` + +It is very slow to instantiate `Error` instances in JavaScript, as the initialization process snapshots the call stack. The magnitude of this performance little value and makes the "validation error" code path unacceptably slow. + +In Zod 4 the `ZodError` class no longer extends the plain JavaScript `Error` class. Any code that relies on `instanceof Error` will need to be refactored. + +```ts +try { + z.string().parse(data) +} catch(err) { + if (err instanceof Error) { + // handle regular Error + } else if (err instanceof z.ZodError) { + // handle ZodError + } +} +``` + + +### 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 + | z.core.$ZodIssueInvalidElement + | 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 // now z.core.$ZodIssueInvalidStringFormat + | z.ZodNotMultipleOfIssue // renamed to z.core.$ZodIssueNotMultipleOf + | z.ZodUnrecognizedKeysIssue // renamed to z.core.$ZodIssueUnrecognizedKeys + | z.ZodInvalidEnumValueIssue // ❌ merged in z.core.$ZodIssueInvalidValue + | z.ZodInvalidLiteralIssue // ❌ merged into z.core.$ZodIssueInvalidValue + | z.ZodInvalidUnionIssue // renamed to z.core.$ZodIssueInvalidUnion + | 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) + | z.ZodCustomIssue // renamed to z.core.$ZodIssueCustom +``` + + +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](/formatting-errors) for more information. + +### Deprecated: `.flatten()` + +The `.flatten()` method on `ZodError` has also been deprecated. Instead use the top-level `z.treeifyError()` function. Read the [Formatting errors docs](/formatting-errors) for more information. + +### drops `.formErrors` + +This API was identical to `.flatten()`. It exists for historical reasons and isn't documented. + +### deprecated `.addIssue()` and `.addIssues()` + +Directly push to `err.issues` array instead, if necessary. + +```ts +myError.issues.push({ + // new issue +}); +``` + +## `z.number()` + +### no infinite values + +`POSITIVE_INFINITY` and `NEGATIVE_INFINITY` are no longer considered valid values for `z.number()`. + +### `.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. + + +## `z.string()` updates +{/* ### deprecates `.email()` etc + +String formats are now represented as schema classes of their own that extend `ZodString`. As such, these APIs have been moved to the top-level `z` namespace. + +```ts +z.email(); +z.uuid(); +z.url(); +z.emoji(); // validates a single emoji character +z.base64(); +z.nanoid(); +z.cuid(); +z.cuid2(); +z.ulid(); +z.ipv4(); +z.ipv6(); +z.cidr(); // 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. They may be removed in a future version. */} + +### 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.string().ipv4() // ✅ +z.string().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 `.cidrv4()` methods. Use `z.union()` to combine them if you need to accept both. + +```ts +z.string().cidr() // ❌ +z.string().cidrv4() // ✅ +z.string().cidrv6() // ✅ +``` + +## `z.coerce` updates + +The input type of all coerced booleans is now `unknown`. + +```ts +const schema = z.coerce.string(); +type schemaInput = z.input; + +// Zod 3: string; +// Zod 4: unknown; +``` + +## `z.object()` + +These modifier methods on the `ZodObject` class determine how the schema handles unknown keys. In Zod 4, this functionality now exists in top-level functions. This aligns better with Zod's declarative-first philosophy, and puts all object variants on equal footing. + + +### deprecates `.strict()` + +```ts +// Zod 3 +z.object({ name: z.string() }).strict(); + +// Zod 4 +z.strictObject({ name: z.string() }); +``` + +### deprecates `.passthrough()` + +```ts +// Zod 3 +z.object({ name: z.string() }).passthrough(); + +// Zod 4 +z.looseObject({ name: z.string() }); +``` + +### deprecates `.strip()` + +This was never particularly useful, as it was the default behavior of `z.object()`. + + +The equivalents for `z.interface()` also exist. Read more about `z.interface()` [here](/api#objects). + +```ts +z.interface({ name: z.string() }); +z.looseInterface({ name: z.string() }); +z.strictInterface({ name: z.string() }); +``` + + +### 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 it's implementation, and it's 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 }; +``` + +## `z.nativeEnum()` deprecated + +The `z.nativeEnum()` method is deprecated in favor of just `z.enum()`. The `z.enum()` API has been overloaded to support both. + +```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 redundunant 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; +// 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. + + + +```ts +const myFunction = z.function().args({ + 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.`; +}); +``` + + +```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.`; +}); +``` + + + +### 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 discussd 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; +// Zod 3: `string` +// Zod 4: still `unknown` +``` + +## `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. + +## `.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.discriminatedUnion()` + +You no longer need to specify a discriminator key (though you still can if you wish; it is ignored). + +```ts +in Zod 4: +const myUnion = z.discriminatedUnion([ + z.object({ type: z.literal("a"), a: z.string() }), + z.object({ type: z.literal("b"), b: z.number() }), +]); + +// in Zod 3: +const myUnion = z.discriminatedUnion("type", [ + z.object({ type: z.literal("a"), a: z.string() }), + z.object({ type: z.literal("b"), b: z.number() }), +]); +``` + +## `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 exhaustiveneess; 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; } +``` + +## `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 { + // ... +} + +// Zod 4 +class ZodType { + // ... +} +``` + +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 +const inferSchema(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/core` package, to facilitate code sharing between `zod` and `@zod/mini`. The contents of `@zod/core` from `zod`/`@zod/mini` using the `z.core` namespace. Check `z.core` if any internal APIs you rely on are missing; they've likely been moved there. + +```ts +import * as z from "zod"; + +function handleError(iss: z.core.utils.$ZodError) { + // do stuff +} +``` + +### 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. + + +{/* The core library also implements the base classes of all schemas (`$ZodType` base class, plus `$ZodString`, `$ZodNumber`, etc), utility functions/types, issue types (`$ZodIssueInvalidType`, etc.), and the base error class (`$ZodError`). If you are building on top of Zod, using the core classes is recommended over using the `zod`-specific classes like (`ZodType`, `ZodString`, etc.). */} + +{/* ## The `@zod/core` package + +A new `@zod/mini` package is being released concurrently with Zod 4. This is a new package optimized for tree-shakability and intended for use in bundle-size-constrained applications. + +To facilitate code sharing, both `zod` and `@zod/mini` have a dependency on a new common core library called `@zod/core`. This library implements the base classes of all schemas (`$ZodType` base class, plus `$ZodString`, `$ZodNumber`, etc), utility functions/types, issue types (`$ZodIssueInvalidType`, etc.), and the base error class (`$ZodError`). + +As you can see these things are all prefixed with `$` to indicate they are implemented in `@zod/core`. Many things formerly exported from `zod` have been moved to `@zod/core`. You can access the contents of `@zod/core` from `zod`/`@zod/mini` using the `z.core` namespace. + +```ts +import * as z from "zod"; + +function handleIssue(iss: z.core.$ZodIssue) { + // do stuff +} +``` */} + +### 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 +``` + +### drops `ZodPreprocess` + +As with `.transform()`, the `z.prepreprocess()` function now returns a `ZodPipe` instance instead of a dedicated `ZodPreprocess` instance. + +```ts +z.preprocess(val => val, z.string()); // ZodPipe +``` + +### 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 */} + +{/* - ZodObject `.keyof()` now returns `ZodLiteral` not `ZodEnum` */} + + + +{/* ## 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 */} diff --git a/packages/docs/content/v4/index.mdx b/packages/docs/content/v4/index.mdx new file mode 100644 index 0000000000..60298b8a60 --- /dev/null +++ b/packages/docs/content/v4/index.mdx @@ -0,0 +1,888 @@ +--- +title: Introducing Zod 4 beta +--- + +import { Callout } from "fumadocs-ui/components/callout"; +import { Tabs, Tab } from "fumadocs-ui/components/tabs"; + +> Refer to the [Changelog](/v4/changelog) for a complete list of breaking changes. + +Zod 4 is now in beta after over a year of active development. It's faster, slimmer, more `tsc`-efficient, and implements some long-requested features. + +To try the beta: + +``` +pnpm upgrade zod@next +``` + +Development will continue on the [`v4`](https://github.com/colinhacks/zod/tree/v4) branch over a 4-6 week beta period as I work with libraries to ensure day-one compatibility with the first stable release. + + + +Huge thanks to [Clerk](https://go.clerk.com/zod-clerk), who 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. + + +## 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 36.5k stars and 23M weekly downloads. After 24 minor versions, the Zod 3 codebase is essentially "maxxed out"; the most commonly requested features and usability improvements require some sort of breaking change. + +Zod 4 implements all of these in one fell swoop. It uses an entirely new internal architecture that solves some long-standing design limitations, lays the groundwork for some long-requested features, and 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 a 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 +$ pnpm install +``` + +Then to run a particular benchmark: + +```sh +$ pnpm bench +``` + +### 2.6x faster string parsing + +```sh +$ pnpm bench string +runtime: node v22.13.0 (arm64-darwin) + +benchmark time (avg) (min … max) p75 p99 p999 +------------------------------------------------- ----------------------------- +• z.string().parse +------------------------------------------------- ----------------------------- +zod3 348 µs/iter (299 µs … 743 µs) 362 µs 494 µs 634 µs +zod4 132 µs/iter (108 µs … 348 µs) 162 µs 269 µs 322 µs + +summary for z.string().parse + zod4 + 2.63x faster than zod3 +``` + +### 3x faster array parsing + +```sh +$ pnpm bench array +runtime: node v22.13.0 (arm64-darwin) + +benchmark time (avg) (min … max) p75 p99 p999 +------------------------------------------------- ----------------------------- +• z.array() parsing +------------------------------------------------- ----------------------------- +zod3 162 µs/iter (141 µs … 753 µs) 152 µs 291 µs 513 µs +zod4 54'282 ns/iter (47'084 ns … 669 µs) 50'833 ns 185 µs 233 µs + +summary for z.array() parsing + zod4 + 2.98x faster than zod3 +``` + +### 7x faster object parsing + +This runs the [Moltar validation library benchmark](https://moltar.github.io/typescript-runtime-type-benchmarks/). + +```sh +$ pnpm bench object-moltar +benchmark time (avg) (min … max) p75 p99 p999 +------------------------------------------------- ----------------------------- +• z.object() safeParse +------------------------------------------------- ----------------------------- +zod3 767 µs/iter (735 µs … 3'136 µs) 775 µs 898 µs 3'136 µs +zod4 110 µs/iter (102 µs … 1'291 µs) 105 µs 217 µs 566 µs + +summary for z.object() safeParse + zod4 + 6.98x faster than zod3 +``` + +## 20x 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@3` results in >25000 type instantiations. With `zod@4` it only results in ~1100. + + + +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 +$ pnpm bench object-with-extend +``` + + +More importantly, Zod 4 has redesigned and simplified the generics of `ZodObject` and other schema classes to avoid some pernicioius "instantiation explosions". The following script contains a number of chained calls to `.extend()` and `.omit()`. + +```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. + +## 30% reduction in core bundle size + +Consider the following simple script. + +```ts +import * as z from "zod"; + +const schema = z.string().min(5); + +schema.parse("hello world"); +``` + +Let's 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` | `8.77kb` | + + +The size of the "core bundle" (the code that will get bundled in the most minimal cases) is ~30% smaller in Zod 4. That's good! + +But ultimately Zod's method-heavy API is fundamentally difficult to tree-shake. Writing slimmer implementations can only get you so far. This brings us to the biggest announcement of Zod 4: `@zod/mini`. + +## Introducing `@zod/mini` + +The `@zod/mini` package provides a functional, tree-shakable API that corresponds one-to-one onto the existing `zod` API. Use the tabs to compare the two APIs. + + + +```ts +import * as z from "@zod/mini"; + +z.optional(z.string()); +z.union([z.string(), z.number()]); +z.string().check(z.refine(val => val.includes("@"))); +z.array(z.number()).check(z.minLength(5), z.maxLength(10)); +z.extend(z.object({ name: z.string() }), { age: z.number() }); +``` + + +```ts +import * as z from "zod"; + +z.string().optional(); +z.string().or(z.number()); +z.string().refine(val => val.includes("@")); +z.array(z.number()).min(5).max(10); +z.object({ name: z.string() }).extend({ age: z.number() }); +``` + + + +Everything in `zod` can be represented in `@zod/mini`; their APIs correspond one-to-one.But instead of Zod's fluent/chainable API, `@zod/mini` is more function-forward. + +The more functional API makes it easier for bundlers to tree-shaking the APIs you don't use. While `zod` is still recommended for the majority of use cases, any projects with uncommonly strict bundle size constraints should consider `@zod/mini`. + +Here's the script from above, updated to use `"@zod/mini"` instead of `"zod"`. + +```ts +import * as z from "@zod/mini"; + +const schema = z.string().check(z.minLength(5)); +schema.parse("hello world"); +``` + +When we build this with `rollup`, the gzipped bundle size is `2.23kb`. That's a 5.7x reduction in bundle size compared to `zod@3`. + +| Package | Bundle (gzip) | +|-----------|--------------| +| `zod@3` | `12.47kb` | +| `zod@4` | `8.77kb` | +| `@zod/mini` | `2.23kb` | + +The `@zod/mini` package doesn't have its own documentation. Instead, all code blocks in the [Defining schemas](/api) now have separate code tabs for `zod` and `@zod/mini`. + +## `z.interface()` + +Zod 4 introduces a new API for defining object types: `z.interface()`. This may seem surprising or confusing, so I'll briefly explain the reasoning here. (A full blog post on this topic is coming soon.) + +### Exact(er) optional properties + +In TypeScript a property can be "optional" in two disctint ways: + +```ts +type KeyOptional = { prop?: string }; +type ValueOptional = { prop: string | undefined }; +``` + +In `KeyOptional`, the `prop` key can be omitted from the object ("key optional"). In `ValueOptional`, the `prop` key *must be set* however it can be set to `undefined` ("value optional"). + +Zod v3 cannot represent `ValueOptional`. Instead, `z.object()` automatically adds question marks to any key that accepts a value of `undefined`: + +```ts +z.object({ name: z.string().optional() }); +// { name?: string | undefined } + +z.object({ name: z.union([z.string(), z.undefined()]) }); +// { name?: string | undefined } +``` + +This includes special schema types like `z.unknown()`: + +```ts +z.object({ name: z.unknown() }); // { name?: unknown } +z.object({ name: z.any() }); // { name?: any } +``` + +To properly represent "key optionality", Zod needed an *object-level* API for marking keys as optional, instead of trying to guess based on the value schema. + +This is why Zod 4 introduces a new API for defining object types: `z.interface()`. + +```ts +const ValueOptional = z.interface({ name: z.string().optional()}); +// { name: string | undefined } + +const KeyOptional = z.interface({ "name?": z.string() }); +// { name?: string } +``` + +Key optionality is now defined with a `?` suffix in the key itself. This way, you have the power to differentiate between key- and value-optionality. + +> Besides this syntactic change, `z.object()` and `z.interface()` are functionality identical. They even use the same parser internally. + +The `z.object()` API is *not deprecated*; feel free to continue using it if you prefer it! For the sake of backwards compatibility `z.interface()` was added as an opt-in API. + +### True recursive types + +But wait there's more! After implementing `z.interface()`, I had a huge realization. The `?`-suffix API in `z.interface()` lets Zod sidestep a TypeScript limitation that long prevented Zod from cleanly representing recursive (cyclical) types. Take this example from the old Zod 3 docs: + +```ts +import * as z from "zod"; // zod@3 + +interface Category { + name: string; + subcategories: Category[]; +}; + +const Category: z.ZodType = z.object({ + name: z.string(), + subcategories: z.lazy(() => Category.array()), +}); +``` + +This has been a thorn in my side for years. To define a cyclical object type, you must a) define a redundant interface, b) use `z.lazy()` to avoid reference errors, and c) cast your schema to `z.ZodType`. This is terrible. + +Here's the same example in Zod 4: + +```ts +import * as z from "zod"; // zod@4 + +const Category = z.interface({ + name: z.string(), + get subcategories() { + return z.array(Category) + } +}); +``` + +No casting, no `z.lazy()`, no redundant type signatures. Just use [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) to define any cyclical properties. The resulting instance has all the object methods you expect: + +```ts +Category.pick({ subcategories: true }); +``` + +This means Zod can finally represent commonly cyclical data structure like ORM schemas, GraphQL types, etc. + +> Given it's ability to represent both cyclical types and more exact optionality, I recommend always using `z.interface()` over `z.object()` without reservation. That said, `z.object()` will never be deprecated or removed, so feel free to keep using it if you prefer. + +## 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 adddress", description: "..." }); +myRegistry.get(emailSchema); +// => { title: "Email adddress", ... } +``` + +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 adddress", 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"], + // ... +}); +``` + + +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" }); +``` + + +## 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().meta({ description: "First name" }), + lastName: z.string().meta({ description: "Last name" }), + age: z.number().meta({ description: "Age" }), +}); + +z.toJSONSchema(mySchema); +// => { +// type: "object", +// properties: { +// name: { title: "Your full name", type: "string" }, +// email: { title: "Email", type: "string", format: "email" }, +// password: { title: "Password", type: "string", minLength: 8 } +// }, +// required: [ "name", "email", "password" ] +// } +``` + +Refer to the [JSON Schema docs](/json-schema) for information on customizing the generated JSON Schema. + + +## 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.type("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"; + +// configure English locale (default) +z.config(z.core.locales.en()); +``` + +At the time of this writing only the English locale is available; I'll be doing a call for PRs once the beta is published. I'll update this section with a list of supported languages as they become available. + +## Error pretty-printing + +The success 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, received number + → at username +✖ Invalid input: expected number, received string, received string + → at favoriteNumbers[1] +``` + +At the moment this isn't configurable; this may change in the future. + +## Top-level string formats + +The following string formats have been hoisted to be top-level APIs 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. + +```ts +z.email(); +z.uuidv4(); +z.uuidv7(); +z.uuidv8(); +z.ipv4(); +z.ipv6(); +z.e164(); +z.base64(); +z.jwt(); +z.ascii(); +z.utf8(); +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 propertly 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 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 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 it's 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("enable") // => 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#stringbools) 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 union support has improved in a couple ways. First, you no longer need to specify the discriminator key. Zod now has a robust way to identify the discriminator key automatically. If no shared discriminator key is found, Zod will throw an error at schema initialization time. + +```ts +// in Zod 4: +const myUnion = z.discriminatedUnion([ + z.object({ type: z.literal("a"), a: z.string() }), + z.object({ type: z.literal("b"), b: z.number() }), +]); + +// in Zod 3: +const myUnion = z.discriminatedUnion("type", [ + z.object({ type: z.literal("a"), a: z.string() }), + z.object({ type: z.literal("b"), b: z.number() }), +]); +``` + +Discriminated unions schema now finally *compose*—you can use one discriminated union as a member of another. Zod determines the optimal discrimination strategy. + +```ts +const BaseError = z.object({ status: z.literal("failed"), message: z.string() }); +const MyErrors = z.discriminatedUnion([ + BaseError.extend({ code: z.literal(400) }), + BaseError.extend({ code: z.literal(401) }), + BaseError.extend({ code: z.literal(500) }) +]); + +const MyResult = z.discriminatedUnion([ + z.interface({ status: z.literal("success"), data: z.string() }), + MyErrors +]); +``` + +## 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 now 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 +``` + +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 +``` + +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/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 third package `@zod/core` that 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/core` provides. Don't hesistate 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 and rationale behind some major features like `@zod/mini` and `z.interface()`. I'll update this section as those get posted. + +Zod 4 will remain in beta for roughly 6 weeks as I work with library authors and major adopters to ensure a smooth day-one transition from Zod 3 to Zod 4. I encourage all users of Zod to upgrade their installation and provide feedback during the beta window. + +```sh + pnpm upgrade zod@next +``` + +Happy parsing!