隆重推出:Zod 4
](https://zod.dev/</>)
](https://zod.dev/</>)
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/v4
和 zod/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 值(false
、undefined
、null
、0
、""
、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_error
和 required_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
]);