Zod logoZod logo](https://zod.dev/</>) Zod logoZod logo](https://zod.dev/</>) Zod 4 Zod 4 The latest version of Zod Search ⌘``K Zod 4 Introducing Zod 4Migration guide Documentation IntroBasic usageDefining schemasCustomizing errorsFormatting errorsMetadata and registriesNewJSON SchemaNewEcosystemFor library authors Packages ZodZod MiniNewZod CoreNew github logo On this page

隆重推出 Zod 4

经过一年的积极开发,Zod 4 现在已经稳定!它更快、更精简、tsc 效率更高,并实现了一些长期需要的功能。

❤️

非常感谢 Clerk,他们通过极其慷慨的 OSS Fellowship 支持了我对 Zod 4 的工作。在(比预期长得多的!)开发过程中,他们一直是一个了不起的合作伙伴。

为了简化用户和 Zod 相关库生态系统的迁移过程,Zod 4 与 Zod 3 一起作为 zod@3.25 版本的一部分发布。要升级:

npm upgrade zod@^3.25.0

然后从 "/v4" 子路径导入 Zod 4:

import { z } from "zod/v4";

有关破坏性更改的完整列表,请参阅 Migration guide。此页面介绍了新功能和改进。

为什么需要一个新的主要版本?

Zod v3.0 发布于 2021 年 5 月 (!)。那时 Zod 在 GitHub 上有 2700 个 star 和 60 万次每周下载。今天,它有 37.8k 个 star 和 3100 万次每周下载(高于 6 周前测试版发布时的 2300 万次!)。经过 24 个小版本,Zod 3 代码库已经达到了瓶颈;最常见的需求的功能和改进需要进行破坏性更改。

Zod 4 一举解决了 Zod 3 的许多长期存在的的设计限制,为几个长期需要的功能和性能的巨大飞跃铺平了道路。它关闭了 Zod 的 10 个最受好评的未解决问题 中的 9 个。希望它能成为未来多年的新基础。

有关新功能的扫描式分解,请参阅目录。单击任何项目以跳转到该部分。

基准测试

您可以在 Zod 存储库中自行运行这些基准测试:

$ git clone git@github.com:colinhacks/zod.git
$ cd zod
$ git switch v4
$ pnpm install

然后运行特定的基准测试:

$ pnpm bench <name>

字符串解析速度提高 14 倍

$ pnpm bench string
runtime: node v22.13.0 (arm64-darwin)
benchmark   time (avg)       (min … max)    p75    p99   p999
------------------------------------------------- -----------------------------
• z.string().parse
------------------------------------------------- -----------------------------
zod3     363 µs/iter    (338 µs … 683 µs)  351 µs  467 µs  572 µs
zod4    24'674 ns/iter  (21'083 ns … 235 µs) 24'209 ns 76'125 ns  120 µs
summary for z.string().parse
 zod4
  14.71x faster than zod3

数组解析速度提高 3 倍

$ pnpm bench array
runtime: node v22.13.0 (arm64-darwin)
benchmark   time (avg)       (min … max)    p75    p99   p999
------------------------------------------------- -----------------------------
• z.array() parsing
------------------------------------------------- -----------------------------
zod3     147 µs/iter    (137 µs … 767 µs)  140 µs  246 µs  520 µs
zod4    19'817 ns/iter  (18'125 ns … 436 µs) 19'125 ns 44'500 ns  137 µs
summary for z.array() parsing
 zod4
  7.43x faster than zod3

对象解析速度提高 6.5 倍

这运行了 Moltar 验证库基准测试

$ pnpm bench object-moltar
benchmark   time (avg)       (min … max)    p75    p99   p999
------------------------------------------------- -----------------------------
• z.object() safeParse
------------------------------------------------- -----------------------------
zod3     805 µs/iter   (771 µs … 2'802 µs)  804 µs  928 µs 2'802 µs
zod4     124 µs/iter   (118 µs … 1'236 µs)  119 µs  231 µs  329 µs
summary for z.object() safeParse
 zod4
  6.5x faster than zod3

tsc 实例化减少 100 倍

考虑以下简单文件:

import { z } from "zod/v4";
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(),
});

使用 "zod/v3" 使用 tsc --extendedDiagnostics 编译此文件会导致 >25000 个类型实例化。使用 "zod/v4" 只会导致约 175 个。

Zod 存储库包含一个 tsc 基准测试环境。使用 packages/tsc 中的编译器基准测试亲自尝试一下。随着实现的演变,确切的数字可能会发生变化。

$ cd packages/tsc
$ pnpm bench object-with-extend

更重要的是,Zod 4 重新设计并简化了 ZodObject 和其他 schema 类的泛型,以避免一些有害的“实例化爆炸”。例如,重复链式调用 .extend().omit()——这以前会导致编译器问题:

import { z } from "zod/v4";
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(),
});

在 Zod 3 中,这需要 4000ms 才能编译;添加对 .extend() 的额外调用会触发“可能无限”的错误。在 Zod 4 中,这在 400ms 内编译完成,速度提高了 10 倍

再加上即将推出的 tsgo 编译器,Zod 4 的编辑器性能将扩展到更大的 schemas 和代码库。

核心包大小减少 2 倍

考虑以下简单脚本。

import { z } from "zod/v4";
const schema = z.boolean();
schema.parse(true);

当涉及到验证时,它就像它得到的一样简单。这是有意的;这是一个衡量 core bundle size 的好方法——即使在简单的情况下,最终也会出现在 bundle 中的代码。我们将使用 Zod 3 和 Zod 4 使用 rollup 将其捆绑在一起,并比较最终的 bundles。

Package| Bundle (gzip) ---|--- zod/v3| 12.47kb zod/v4| 5.36kb 核心包在 Zod 4 中缩小了约 57%(2.3 倍)。这很好!但我们可以做得更好。

隆重推出 Zod Mini

Zod 的方法繁重的 API 从根本上难以进行 tree-shake。即使我们简单的 z.boolean() 脚本也会引入我们未使用的一堆方法的实现,例如 .optional().array() 等。编写更精简的实现只能让你走这么远。这就是 zod/v4-mini 的用武之地。

npm install zod@^3.25.0

它是一个 Zod 变体,具有与 zod 一一对应的功能性、可 tree-shaking 的 API。在 Zod 使用方法的地方,zod/v4-mini 通常使用包装函数: Zod MiniZod

import { z } from "zod/v4-mini";
z.optional(z.string());
z.union([z.string(), z.number()]);
z.extend(z.object({ /* ... */ }), { age: z.number() });

并非所有方法都消失了!解析方法在 zod/v4zod/v4-mini 中是相同的。

import { z } from "zod/v4-mini";
z.string().parse("asdf");
z.string().safeParse("asdf");
await z.string().parseAsync("asdf");
await z.string().safeParseAsync("asdf");

还有一个通用的 .check() 方法,用于添加 refinements。 Zod MiniZod

import { z } from "zod/v4-mini";
z.array(z.number()).check(
 z.minLength(5),
 z.maxLength(10),
 z.refine(arr => arr.includes(5))
);

以下顶级 refinements 在 zod/v4-mini 中可用。它们对应的哪些方法应该是不言自明的。

import { z } from "zod/v4-mini";
// custom checks
z.refine();
// first-class checks
z.lt(value);
z.lte(value); // alias: z.maximum()
z.gt(value);
z.gte(value); // alias: z.minimum()
z.positive();
z.negative();
z.nonpositive();
z.nonnegative();
z.multipleOf(value);
z.maxSize(value);
z.minSize(value);
z.size(value);
z.maxLength(value);
z.minLength(value);
z.length(value);
z.regex(regex);
z.lowercase();
z.uppercase();
z.includes(value);
z.startsWith(value);
z.endsWith(value);
z.property(key, schema); // for object schemas; check `input[key]` against `schema`
z.mime(value); // for file schemas (see below)
// overwrites (these *do not* change the inferred type!)
z.overwrite(value => newValue);
z.normalize();
z.trim();
z.toLowerCase();
z.toUpperCase();

这种更具函数性的 API 使 bundler 更容易 tree-shaking 您不使用的 API。虽然仍然建议在大多数用例中使用 zod/v4,但任何对 bundle size 有异常严格限制的项目都应考虑 zod/v4-mini

核心包大小减少 6.6 倍

以下是上面的脚本,已更新为使用 "zod/v4-mini" 而不是 "zod"

import { z } from "zod/v4-mini";
const schema = z.boolean();
schema.parse(false);

当我们使用 rollup 构建它时,gzipped bundle size 为 1.88kb。与 zod@3 相比,核心包大小减少了 85%(6.6 倍)。 Package| Bundle (gzip) ---|--- zod/v3| 12.47kb zod/v4| 5.36kb zod/v4-mini| 1.88kb 在专用的 zod/v4-mini 文档页面上了解更多信息。完整的 API 详细信息混合到现有的文档页面中;无论其 API 有何不同,代码块都包含 "Zod""Zod Mini" 的单独选项卡。

元数据

Zod 4 引入了一个新系统,用于将强类型元数据添加到您的 schemas。元数据不存储在 schema 本身中;而是存储在“schema 注册表”中,该注册表将 schema 与某些类型化元数据相关联。要使用 z.registry() 创建注册表:

import { z } from "zod/v4";
const myRegistry = z.registry<{ title: string; description: string }>();

要将 schemas 添加到您的注册表:

const emailSchema = z.string().email();
myRegistry.add(emailSchema, { title: "Email address", description: "..." });
myRegistry.get(emailSchema);
// => { title: "Email address", ... }

或者,您可以方便地使用 schema 上的 .register() 方法:

emailSchema.register(myRegistry, { title: "Email address", description: "..." })
// => returns emailSchema

全局注册表

Zod 还导出一个全局注册表 z.globalRegistry,它接受一些常见的 JSON Schema 兼容元数据:

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

要方便地将 schema 添加到 z.globalRegistry,请使用 .meta() 方法。

z.string().meta({
 id: "email_address",
 title: "Email address",
 description: "Provide your email",
 examples: ["naomie@example.com"],
 // ...
});

为了与 Zod 3 兼容,.describe() 仍然可用,但首选 .meta()

z.string().describe("An email address");
// equivalent to
z.string().meta({ description: "An email address" });

JSON Schema 转换

Zod 4 通过 z.toJSONSchema() 引入了第一方 JSON Schema 转换。

import { z } from "zod/v4";
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"],
// }

z.globalRegistry 中的任何元数据都会自动包含在 JSON Schema 输出中。

const mySchema = z.object({
 firstName: z.string().describe("Your first name"),
 lastName: z.string().meta({ title: "last_name" }),
 age: z.number().meta({ examples: [12, 99] }),
});
z.toJSONSchema(mySchema);
// => {
//  type: 'object',
//  properties: {
//   firstName: { type: 'string', description: 'Your first name' },
//   lastName: { type: 'string', title: 'last_name' },
//   age: { type: 'number', examples: [ 12, 99 ] }
//  },
//  required: [ 'firstName', 'lastName', 'age' ]
// }

有关自定义生成的 JSON Schema 的信息,请参阅 JSON Schema docs

递归对象

这是一个意想不到的事情。经过多年的尝试来解决这个问题,我终于 找到了一种方法 在 Zod 中正确推断递归对象类型。要定义递归类型:

const Category = z.object({
 name: z.string(),
 get subcategories(){
  return z.array(Category)
 }
});
type Category = z.infer<typeof Category>;
// { name: string; subcategories: Category[] }

您还可以表示 mutually recursive types

const User = z.object({
 email: z.email(),
 get posts(){
  return z.array(Post)
 }
});
const Post = z.object({
 title: z.string(),
 get author(){
  return User
 }
});

与 Zod 3 的递归类型模式不同,不需要类型转换。生成的 schemas 是普通的 ZodObject 实例,并具有完整的可用方法集。

Post.pick({ title: true })
Post.partial();
Post.extend({ publishDate: z.date() });

文件 schemas

要验证 File 实例:

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

国际化

Zod 4 引入了一个新的 locales API,用于将错误消息全局翻译成不同的语言。

import { z } from "zod/v4";
// configure English locale (default)
z.config(z.locales.en());

在撰写本文时,仅提供英语区域设置;稍后将从社区征集 pull request;此部分将随着受支持的语言的可用而更新。

错误漂亮打印

zod-validation-error 包的受欢迎程度表明,对用于漂亮打印错误的官方 API 存在大量需求。如果您当前正在使用该包,请务必继续使用它。

Zod 现在实现了一个顶级 z.prettifyError 函数,用于将 ZodError 转换为用户友好的格式化字符串。

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

这将返回以下漂亮的、可打印的多行字符串:

✖ Unrecognized key: "extraField"
✖ Invalid input: expected string, received number
 → at username
✖ Invalid input: expected number, received string
 → at favoriteNumbers[1]

目前,格式是不可配置的;将来可能会改变。

顶级字符串格式

所有“字符串格式”(email 等)都已提升为 z 模块上的顶级函数。这既更简洁又更易于 tree-shaking。方法等效项(z.string().email() 等)仍然可用,但已弃用。它们将在下一个主要版本中删除。

z.email();
z.uuidv4();
z.uuidv7();
z.uuidv8();
z.ipv4();
z.ipv6();
z.cidrv4();
z.cidrv6();
z.url();
z.e164();
z.base64();
z.base64url();
z.jwt();
z.ascii();
z.utf8();
z.lowercase();
z.iso.date();
z.iso.datetime();
z.iso.duration();
z.iso.time();

自定义 email 正则表达式

z.email() API 现在支持自定义正则表达式。没有一个规范的 email 正则表达式;不同的应用程序可能会选择更严格或更宽松。为方便起见,Zod 导出了一些常见的正则表达式。

// Zod 的默认 email 正则表达式(Gmail 规则)
// see colinhacks.com/essays/reasonable-email-regex
z.email(); // z.regexes.email
// 浏览器用于验证 input[type=email] 字段的正则表达式
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email
z.email({ pattern: z.regexes.html5Email });
// 经典的 emailregex.com 正则表达式 (RFC 5322)
z.email({ pattern: z.regexes.rfc5322Email });
// 一个允许 Unicode 的宽松正则表达式(适用于 intl email)
z.email({ pattern: z.regexes.unicodeEmail });

模板字面量类型

Zod 4 实现了 z.templateLiteral()。模板字面量类型可能是 TypeScript 类型系统最大的特性,以前无法表示。

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

每个可以字符串化的 Zod schema 类型都存储一个内部正则表达式:字符串、字符串格式(如 z.email())、数字、布尔值、bigint、枚举、字面量、未定义/可选、null/nullable 和其他模板字面量。z.templateLiteral 构造函数将这些连接成一个超级正则表达式,因此像字符串格式(z.email())这样的东西可以被正确执行(但自定义 refinements 不行!)。

阅读 模板字面量文档 以获取更多信息。

数字格式

添加了新的数值“格式”,用于表示固定宽度的整数和浮点类型。这些返回一个 ZodNumber 实例,其中已经添加了适当的最小/最大约束。

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]

类似地,还添加了以下 bigint 数值格式。这些整数类型超过了 JavaScript 中 number 可以安全表示的范围,因此这些返回一个 ZodBigInt 实例,其中已经添加了适当的最小/最大约束。

z.int64();  // [-9223372036854775808n, 9223372036854775807n]
z.uint64();  // [0n, 18446744073709551615n]

Stringbool

现有的 z.coerce.boolean() API 非常简单:falsy 值(falseundefinednull0""NaN 等)变为 false,truthy 值变为 true

这仍然是一个很好的 API,并且它的行为与其他 z.coerce API 一致。但是一些用户要求更复杂的“env 风格”布尔强制转换。为了支持这一点,Zod 4 引入了 z.stringbool()

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" }]>

要自定义 truthy 和 falsy 值:

z.stringbool({
 truthy: ["yes", "true"],
 falsy: ["no", "false"]
})

有关更多信息,请参阅 z.stringbool() 文档

简化的错误自定义

Zod 4 中的大多数破坏性更改都涉及 错误自定义 API。它们在 Zod 3 中有点混乱;Zod 4 使事情变得更加优雅,我认为值得在这里强调一下。

长话短说,现在有一个统一的 error 参数来自定义错误,取代了以下 API:

message 替换为 error。(仍然支持 message 参数,但已弃用。)

- z.string().min(5, { message: "Too short." });
+ z.string().min(5, { error: "Too short." });

invalid_type_errorrequired_error 替换为 error(函数语法):

// 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"
+ });

errorMap 替换为 error(函数语法):

// 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}`
+   }
+  },
+ });

升级的 z.discriminatedUnion()

Discriminated unions 现在支持许多以前不支持的 schema 类型,包括 unions、pipes 和嵌套对象:

const MyResult = z.discriminatedUnion("status", [
 // simple literal
 z.object({ status: z.literal("aaa"), data: z.string() }),
 // union discriminator
 z.object({ status: z.union([z.literal("bbb"), z.literal("ccc")]) }),
 // pipe discriminator
 z.object({ status: z.object({ value: z.literal("fail") }) }),
]);

也许最重要的是,discriminated unions 现在可以 compose——您可以使用一个 discriminated union 作为另一个的成员。

const BaseError = z.object({ status: z.literal("failed"), message: z.string() });
const MyErrors = z.discriminatedUnion("code", [
 BaseError.extend({ code: z.literal(400) }),
 BaseError.extend({ code: z.literal(401) }),
 BaseError.extend({ code: z.literal(500) })
]);
const MyResult = z.discriminatedUnion("status", [
 z.object({ status: z.literal("success"), data: z.string() }),
 MyErrors
]);

[z.literal() 中的多个值](https://