zod

Library

colinhacks/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### AO

FAQ (5)

Troubleshooting
Why 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);

GitHub Issue #6646
Troubleshooting
How 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.

GitHub Issue #5528
Troubleshooting
How 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>;
}

GitHub Issue #6637
Troubleshooting
Why 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 ``

GitHub Issue #6597
Troubleshooting
Why 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.

GitHub Issue #6631