zod
Librarycolinhacks/zod
TypeScript-first schema validation with static type inference.
Overview
Zod is a TypeScript-first schema validation library that infers static types from schema definitions. It provides a concise, immutable API to parse and validate untrusted data with strong type safety, zero dependencies, and built-in JSON Schema support. Works in Node.js and modern browsers.
README Preview
\n \n Zod\n \n TypeScript-first schema validation with static type inference\n \n by @colinhacks\n \n\n\n\n\n\n\n\n\n\n\n\n\n Docs\n • \n Discord\n • \n 𝕏\n • \n Bluesky\n \n\n\n\n\n\n### [Read the docs →](https://zod.dev/api)\n\n\n\n\n## What is Zod?\n\nZod is a TypeScript-first validation library. Define a schema and parse some data with it. You'll get back a strongly typed, validated result.\n\n```ts\nimport * as z from "zod";\n\nconst User = z.object({\n name: z.string(),\n});\n\n// some untrusted data...\nconst input = {\n /* stuff */\n};\n\n// the parsed result is validated and type safe!\nconst data = User.parse(input);\n\n// so you can use it with confidence :)\nconsole.log(data.name);\n```\n\n\n\n## Features\n\n- Zero external dependencies\n- Works in Node.js and all modern browsers\n- Tiny: `2kb` core bundle (gzipped)\n- Immutable API: methods return a new instance\n- Concise interface\n- Works with TypeScript and plain JS\n- Built-in JSON Schema conversion\n- Extensive ecosystem\n\n\n\n## Installation\n\n```sh\nnpm install zod\n```\n\n\n\n## Basic usage\n\nBefore you can do anything else, you need to define a schema. For the purposes of this guide, we'll use a simple object schema.\n\n```ts\nimport * as z from "zod";\n\nconst Player = z.object({\n username: z.string(),\n xp: z.number(),\n});\n```\n\n### Parsing data\n\nGiven any Zod schema, use `.parse` to validate an input. If it's valid, Zod returns a strongly-typed _deep clone_ of the input.\n\n```ts\nPlayer.parse({ username: "billie", xp: 100 });\n// => returns { username: "billie", xp: 100 }\n```\n\n**Note** — If your schema uses certain asynchronous APIs like `async` [refinements](https://zod.dev/api#refinements) or [transforms](https://zod.dev/api#transforms), you'll need to use the `.parseAsync()` method instead.\n\n```ts\nconst schema = z.string().refine(async (val) => val.length "hello"\n```\n\n### AOFAQ (5)
TroubleshootingWhy does Zod safeParse error.message return a JSON object instead of the validation error message?
In Zod 4, error.message is a JSON string containing all validation issues because a single ZodError can contain multiple errors. To get the first human-readable message, use:
const parsedData = signupSchema.safeParse(req.body);
if (!parsedData.success) {
const message = parsedData.error.issues[0]?.message;
return errorResponse(res, 400, message);
}
To get all error messages:
const messages = parsedData.error.issues.map(issue => issue.message);
To format the entire error as readable text, use:
z.prettifyError(parsedData.error);TroubleshootingHow do I fix Zod v4 `.refine()` type predicates no longer narrowing the inferred type?
Support for type predicates in .refine() was restored in Zod **v4.2.2** (PR #5575). Upgrade first:
npm install zod@^4.2.2
Your existing v3 pattern should narrow the output type again:
const mySchema = z.string().refine(
(val): val is "specific-value" => checkExternalLogic(val)
);
type MyType = z.infer<typeof mySchema>; // "specific-value"
If you cannot upgrade from an earlier v4 release, use z.custom<T>() for simple predicate-based narrowing:
const mySchema = z.custom<"specific-value">(
(val) => typeof val === "string" && checkExternalLogic(val),
{ message: "Expected specific-value" }
);
Note that z.custom() does not receive a validation ctx, so it cannot add multiple customized issues. For custom error reporting on older v4 versions, use .check() or .transform() with their context API and assert/narrow the output type explicitly. The preferred fix is upgrading to Zod 4.2.2 or later.
TroubleshootingHow do I fix TS5115 'infinitely circular' when using `.catchall(z.json())` with Zod and TypeScript 7.1 nightly?
Upgrade Zod to the release containing PR #6645 (run npm install zod@latest or yarn add zod@latest). The error affects Zod 4.5.4 and 4.6.5 with TypeScript 7.1 dev builds; TypeScript 7.0.2 is unaffected. If you cannot upgrade immediately, either stay on TypeScript 7.0.2 or patch $ZodRecordInternals in v4/core/schemas.d.ts and .d.cts so it extends $ZodTypeInternals without type arguments and declares output and input directly:
export interface $ZodRecordInternals<
Key extends $ZodRecordKey = $ZodRecordKey,
Value extends SomeType = $ZodType,
> extends $ZodTypeInternals {
def: $ZodRecordDef<Key, Value>;
isst: errors.$ZodIssueInvalidType | errors.$ZodIssueInvalidKey<Record<PropertyKey, unknown>>;
optin?: "optional" | undefined;
optout?: "optional" | undefined;
output: $InferZodRecordOutput<Key, Value>;
input: $InferZodRecordInput<Key, Value>;
}TroubleshootingWhy is `import { z } from 'zod'` 4–5x larger than `import * as z` with esbuild, and how do I reduce the Zod v4 bundle?
This affects Zod v4 (reproduced with zod@4.6.4 and esbuild@0.28.2). Zod exports the namespace as export { z, z as default }. Rollup and Webpack can tree-shake that re-export, but esbuild treats the re-exported namespace object as opaque, so both import { z } from 'zod' and the default import retain about 736 KB. Use the namespace import directly: ``ts import * as z from 'zod'; z.object({ x: z.string() }); ` In the reproduction, this reduces output from 736 KB to about 165 KB. Avoid these forms with esbuild: `ts import { z } from 'zod'; import z from 'zod'; ` Granular function imports can also be tree-shaken: `ts import { object, string } from 'zod'; object({ x: string() }); ` If a third-party dependency such as @tanstack/ai internally uses import { z } from 'zod', patch that dependency with patch-package (or maintain a fork/resolution) and change it to import * as z from 'zod'. Vite production builds using Rollup are not affected in the same way; the discrepancy mainly appears when esbuild performs bundling, such as esbuild directly or Vite's dev server. Verify the result with a metafile: `bash npx esbuild app.ts --bundle --platform=node --format=esm --outfile=out.mjs --metafile=meta.json ``
TroubleshootingWhy does my exhaustive Zod v4 visitor miss $ZodFunction and $ZodPrefault?
The core documentation incorrectly omits these members from the documented $ZodTypes union. The authoritative definition in packages/zod/src/v4/core/schemas.ts includes both $ZodFunction and $ZodPrefault. Update an exhaustive visitor or type-level switch to handle $ZodFunction and $ZodPrefault, or derive your cases from the source $ZodTypes instead of copying the documented union. The documentation fix adds both types to $ZodTypes in packages/docs/content/packages/core.mdx and adds $ZodFunction to the inheritance diagram.