Zod logo

Zod 4 简介

经过一年的积极开发:Zod 4 现已稳定!它更快、更精简、更符合 tsc 规范,并且实现了一些长期请求的功能。

¥After a year of active development: Zod 4 is now stable! It's faster, slimmer, more tsc-efficient, and implements some long-requested features.

❤️

非常感谢 Clerk,他们慷慨地提供了 OSS 奖学金 代码,支持我在 Zod 4 上的开发工作。在整个开发过程中(比预期的要长得多!),它们都是非常棒的合作伙伴。

¥Huge thanks to Clerk, who supported my work on Zod 4 through their extremely generous OSS Fellowship. They were an amazing partner throughout the (much longer than anticipated!) development process.

版本控制

¥Versioning

为了简化用户和 Zod 相关库生态系统的迁移过程,Zod 4 将作为 zod@3.25 版本的一部分与 Zod 3 一起发布。尽管版本号不同,但它被认为是稳定的,可以用于生产环境。

¥To simplify the migration process both for users and Zod's ecosystem of associated libraries, Zod 4 will initially published alongside Zod 3 as part of the zod@3.25 release. Despite the version number, it it considered stable and ready for production use.

要升级:

¥To upgrade:

npm upgrade zod@^3.25.0

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

¥Then import Zod 4 from the "/v4" subpath:

import { z } from "zod/v4";

将来,当 Zod 4 获得广泛支持时,我们将在 npm 上发布 zod@4.0.0。此时,Zod 4 将从包根目录 ("zod") 导出。"zod/v4" 子路径将保持可用。有关此版本控制方案原因的详细说明,请参阅 此问题

¥Down the road, when there's broad support for Zod 4, we'll publish zod@4.0.0 on npm. At this point, Zod 4 will be exported from the package root ("zod"). The "zod/v4" subpath will remain available. For a detailed writeup on the reasons for this versioning scheme, refer to this issue.

有关重大变更的完整列表,请参阅 迁移指南。本文重点介绍新功能和增强功能。

¥For a complete list of breaking changes, refer to the Migration guide. This post focuses on new features & enhancements.

为什么要推出新的主要版本?

¥Why a new major version?

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

¥Zod v3.0 was released in May 2021 (!). Back then Zod had 2700 stars on GitHub and 600k weekly downloads. Today it has 37.8k stars and 31M weekly downloads (up from 23M when the beta came out 6 weeks ago!). After 24 minor versions, the Zod 3 codebase had hit a ceiling; the most commonly requested features and improvements require breaking changes.

Zod 4 一举修复了 Zod 3 中许多长期存在的设计限制,为多个长期呼声高涨的功能和性能的巨大飞跃铺平了道路。它关闭了 Zod 的 10 个最多支持的开放问题 中的 9 个。幸运的话,它将成为未来许多年的新基础。

¥Zod 4 fixes a number of long-standing design limitations of Zod 3 in one fell swoop, paving the way for several long-requested features and a huge leap in performance. It closes 9 of Zod's 10 most upvoted open issues. With luck, it will serve as 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

你可以在 Zod 代码库中自行运行这些基准测试:

¥You can run these benchmarks yourself in the Zod repo:

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

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

¥Then to run a particular benchmark:

$ pnpm bench <name>

字符串解析速度提高 14 倍

¥14x faster string parsing

$ 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

数组解析速度提高 7 倍

¥7x faster array parsing

$ 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 倍

¥6.5x faster object parsing

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

¥This runs the Moltar validation library benchmark.

$ 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 倍

¥100x reduction in tsc instantiations

考虑以下简单文件:

¥Consider the following simple file:

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(),
});

使用 tsc --extendedDiagnostics"zod/v3" 编译此文件将生成超过 25000 个类型实例。使用 "zod/v4",结果仅为约 175。

¥Compiling this file with tsc --extendedDiagnostics using "zod/v3" results in >25000 type instantiations. With "zod/v4" it only results in ~175.

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

¥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.

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

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

¥More importantly, Zod 4 has redesigned and simplified the generics of ZodObject and other schema classes to avoid some pernicious "instantiation explosions". For instance, chaining .extend() and .omit() repeatedly—something that previously caused compiler issues:

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 中,这在 400ms10x 中编译速度更快。

¥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.

结合即将推出的 tsgo 编译器,Zod 4 的编辑器性能将扩展到更大的模式和代码库。

¥Coupled with the upcoming tsgo compiler, Zod 4's editor performance will scale to vastly larger schemas and codebases.

核心包大小减少 2 倍

¥2x reduction in core bundle size

考虑以下简单脚本。

¥Consider the following simple script.

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

在验证方面,它非常简单。这是故意为之;它是衡量核心包大小的好方法 - 即使在简单的情况下,最终也会包含在包中的代码。我们将使用 Zod 3 和 Zod 4 将其与 rollup 打包,并比较最终的打包包。

¥It's about as simple as it gets when it comes to validation. That's intentional; it's a good way to measure the core bundle size—the code that will end up in the bundle even in simple cases. We'll bundle this with rollup using both Zod 3 and Zod 4 and compare the final bundles.

打包 (gzip)
zod/v312.47kb
zod/v45.36kb

Zod 4 中的核心包体积缩小了约 57%(2.3 倍)。太棒了!但我们可以做得更好。

¥The core bundle is ~57% smaller in Zod 4 (2.3x). That's good! But we can do a lot better.

Zod Mini 简介

¥Introducing Zod Mini

Zod 的方法密集型 API 从根本上来说很难进行 tree-shaking。即使我们简单的 z.boolean() 脚本也会引入一些我们未使用的方法的实现,例如 .optional().array() 等。编写更精简的实现只能让你走这么远。这就是 zod/v4-mini 的作用所在。

¥Zod's method-heavy API is fundamentally difficult to tree-shake. Even our simple z.boolean() script pulls in the implementations of a bunch of methods we didn't use, like .optional(), .array(), etc. Writing slimmer implementations can only get you so far. That's where zod/v4-mini comes in.

npm install zod@^3.25.0

它是 Zod 的一个变体,具有函数式、可摇树优化的 API,与 zod 一一对应。Zod 使用方法时,zod/v4-mini 通常使用封装函数:

¥It's a Zod variant with a functional, tree-shakable API that corresponds one-to-one with zod. Where Zod uses methods, zod/v4-mini generally uses wrapper functions:

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 中的解析方法相同。

¥Not all methods are gone! The parsing methods are identical in zod/v4 and 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() 方法用于添加改进。

¥There's also a general-purpose .check() method used to add refinements.

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

zod/v4-mini 中提供以下顶层改进。它们对应的方法应该很容易理解。

¥The following top-level refinements are available in zod/v4-mini. It should be fairly self-explanatory which methods they correspond to.

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 使打包器更容易对不使用的 API 进行树形优化。虽然 zod/v4 仍然推荐用于大多数用例,但任何对包大小限制极其严格的项目都应该考虑使用 zod/v4-mini

¥This more functional API makes it easier for bundlers to tree-shake the APIs you don't use. While zod/v4 is still recommended for the majority of use cases, any projects with uncommonly strict bundle size constraints should consider zod/v4-mini.

核心包大小减少 6.6 倍

¥6.6x reduction in core bundle size

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

¥Here's the script from above, updated to use "zod/v4-mini" instead of "zod".

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

当我们使用 rollup 构建此文件时,压缩包大小为 1.88kb。与 zod@3 相比,核心包大小减少了 85%(6.6 倍)。

¥When we build this with rollup, the gzipped bundle size is 1.88kb. That's an 85% (6.6x) reduction in core bundle size compared to zod@3.

打包 (gzip)
zod/v312.47kb
zod/v45.36kb
zod/v4-mini1.88kb

在专门的 zod/v4-mini 文档页面上了解更多信息。完整的 API 详细信息已混合到现有文档页面中;代码块在 "Zod""Zod Mini" 的 API 出现分歧时,会分别包含各自的选项卡。

¥Learn more on the dedicated zod/v4-mini docs page. Complete API details are mixed into existing documentation pages; code blocks contain separate tabs for "Zod" and "Zod Mini" wherever their APIs diverge.

元数据

¥Metadata

Zod 4 引入了一个新系统,用于将强类型元数据添加到你的模式中。元数据不存储在模式本身内;它存储在一个 "模式注册表" 中,该 "模式注册表" 将一个模式与一些类型元数据关联起来。要使用 z.registry() 创建注册表:

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

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

要将架构添加到注册表:

¥To add schemas to your registry:

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

或者,为了方便起见,你可以在模式上使用 .register() 方法:

¥Alternatively, you can use the .register() method on a schema for convenience:

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

全局注册表

¥The global registry

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

¥Zod also exports a global registry z.globalRegistry that accepts some common JSON Schema-compatible metadata:

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

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

¥To conveniently add a schema to z.globalRegistry, use the .meta() method.

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

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

¥For compatibility with Zod 3, .describe() is still available, but .meta() is preferred.

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

JSON Schema 转换

¥JSON Schema conversion

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

¥Zod 4 introduces first-party JSON Schema conversion via z.toJSONSchema().

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 输出中。

¥Any metadata in z.globalRegistry is automatically included in the JSON Schema output.

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 文档 文档。

¥Refer to the JSON Schema docs for information on customizing the generated JSON Schema.

递归对象

¥Recursive objects

这是一个意料之外的疏忽。经过多年的努力,我终于让 找到了方法 能够正确推断 Zod 中的递归对象类型。定义一个递归类型:

¥This was an unexpected one. After years of trying to crack this problem, I finally found a way to properly infer recursive object types in Zod. To define a recursive type:

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

你还可以表示相互递归的类型:

¥You can also represent 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 的递归类型模式不同,此方法无需进行类型转换。生成的 schema 是普通的 ZodObject 实例,并包含所有可用的方法。

¥Unlike the Zod 3 pattern for recursive types, there's no type casting required. The resulting schemas are plain ZodObject instances and have the full set of methods available.

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

文件模式

¥File schemas

要验证 File 实例:

¥To validate File instances:

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 引入了一个新的 locales API,用于将错误消息全局翻译成不同的语言。

¥Zod 4 introduces a new locales API for globally translating error messages into different languages.

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

在撰写本文时,仅支持英语语言环境;社区很快会发起拉取请求;本节将在支持的语言列表可用时进行更新。

¥At the time of this writing only the English locale is available; There will be a call for pull request from the community shortly; this section will be updated with a list of supported languages as they become available.

错误美观打印

¥Error pretty-printing

zod-validation-error 包的流行表明,官方 API 对漂亮打印错误信息的需求巨大。如果你目前正在使用该软件包,请务必继续使用它。

¥The popularity of the 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 现在实现了一个顶层 z.prettifyError 函数,用于将 ZodError 转换为用户友好的格式化字符串。

¥Zod now implements a top-level z.prettifyError function for converting a ZodError to a user-friendly formatted string.

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:

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

目前格式不可配置;这可能会在未来发生变化。

¥Currently the formatting isn't configurable; this may change in the future.

顶层字符串格式

¥Top-level string formats

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

¥All "string formats" (email, etc.) have been promoted to top-level functions 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. They'll be removed in the next major version.

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

自定义电子邮件正则表达式

¥Custom email regex

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

¥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.

// 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://web.nodejs.cn/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 实现了 z.templateLiteral()。模板字面值类型可能是 TypeScript 类型系统中之前无法表示的最大特性。

¥Zod 4 implements z.templateLiteral(). Template literal types are perhaps the biggest feature of TypeScript's type system that wasn't previously representable.

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 模式类型都存储一个内部正则表达式:字符串、类似 z.email() 的字符串格式、数字、布尔值、大整型、枚举、字面量、未定义/可选、空/可空以及其他模板字面量。z.templateLiteral 构造函数将这些连接成一个超级正则表达式,因此像字符串格式 (z.email()) 这样的操作可以得到正确执行(但自定义细化操作则不行!)。

¥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 properly enforced (but custom refinements are not!).

阅读 模板字面量文档 了解更多信息。

¥Read the template literal docs for more info.

数字格式

¥Number formats

添加了新的数字 "formats",用于表示固定宽度的整数和浮点类型。它们返回一个已添加适当最小/最大约束的 ZodNumber 实例。

¥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.

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 实例。

¥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.

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

Stringbool

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

¥The existing z.coerce.boolean() API is very simple: falsy values (false, undefined, null, 0, "", NaN etc) become false, truthy values become true.

这仍然是一个优秀的 API,其行为与其他 z.coerce API 一致。但有些用户要求更复杂的 "env-style" 布尔强制转换。为了支持这一点,Zod 4 引入了 z.stringbool()

¥This is still a good API, and its 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():

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:

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

有关自定义生成的 JSON Schema 的信息,请参阅 z.stringbool() docs

¥Refer to the z.stringbool() docs for more information.

简化错误定制

¥Simplified error customization

Zod 4 中的大部分重大变更都涉及错误自定义 API。它们在 Zod 3 中有点混乱;Zod 4 使一切变得更加优雅,我认为值得在此强调一下。

¥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.

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

¥Long story short, there is now a single, unified error parameter for customizing errors, replacing the following APIs:

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

¥Replace message with error. (The message parameter is still supported but deprecated.)

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

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

¥Replace invalid_type_error and required_error with error (function syntax):

// 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(函数语法):

¥Replace errorMap with error (function syntax):

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

¥Upgraded z.discriminatedUnion()

可区分联合现在支持许多以前不支持的模式类型,包括联合、管道和嵌套对象:

¥Discriminated unions now support a number of schema types not previously supported, including unions, pipes, and nested objects:

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") }) }),
]);

或许最重要的是,可区分联合现在可以组合了 - 你可以将一个可区分联合用作另一个可区分联合的成员。

¥Perhaps most importantly, discriminated unions now compose—you can use one discriminated union as a member of another.

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() 中的多值

¥Multiple values in z.literal()

z.literal() API 现在可选地支持多个值。

¥The z.literal() API now optionally supports multiple values.

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 live inside schemas

在 Zod 3 中,它们存储在封装原始模式的 ZodEffects 类中。这很不方便,因为这意味着你无法将 .refine() 与其他模式方法(例如 .min())交错使用。

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

z.string()
  .refine(val => val.includes("@"))
  .min(5);
// ^ ❌ Property 'min' does not exist on type ZodEffects<ZodString, string, string>

在 Zod 4 中,细化存储在模式本身内部,因此上述代码可以正常工作。

¥In Zod 4, refinements are stored inside the schemas themselves, so the code above works as expected.

z.string()
  .refine(val => val.includes("@"))
  .min(5); // ✅

.overwrite()

.transform() 方法非常有用,但它有一个主要缺点:输出类型在运行时不再可自省。transform 函数是一个可以返回任何值的黑盒。这意味着(除其他因素外)没有可靠的方法将模式转换为 JSON 模式。

¥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.

const Squared = z.number().transform(val => val ** 2);
// => ZodPipe<ZodNumber, ZodTransform>

Zod 4 引入了一种新的 .overwrite() 方法,用于表示不改变推断类型的转换。与 .transform() 不同,此方法返回原始类的实例。覆盖函数存储为改进,因此它不会(也不能)修改推断类型。

¥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.

z.number().overwrite(val => val ** 2).max(100);
// => ZodNumber

现有的 .trim().toLowerCase().toUpperCase() 方法已使用 .overwrite() 重新实现。

¥The existing .trim(), .toLowerCase() and .toUpperCase() methods have been reimplemented using .overwrite().

可扩展的基础:zod/v4/core

¥An extensible foundation: zod/v4/core

虽然这与大多数 Zod 用户无关,但值得强调。zod/v4-mini 的添加使得创建共享子包 zod/v4/core 成为必要,其中包含 zod/v4zod/v4-mini 之间共享的核心功能。

¥While this will not be relevant to the majority of Zod users, it's worth highlighting. The addition of zod/v4-mini necessitated the creation of a shared sub-package zod/v4/core which contains the core functionality shared between zod/v4 and zod/v4-mini.

起初我对此很抗拒,但现在我将其视为 Zod 4 最重要的功能之一。它使 Zod 从一个简单的库升级为一个可以添加到其他库中的快速验证 "substrate"。

¥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.

如果你正在构建一个模式库,请参考 zod/v4zod/v4-mini 的实现,了解如何在 zod/v4/core 提供的基础上进行构建。如有任何帮助或反馈,请随时在 GitHub 讨论区或通过 X/Bluesky 与我们联系。

¥If you're building a schema library, refer to the implementations of zod/v4 and zod/v4-mini to see how to build on top of the foundation zod/v4/core provides. Don't hesitate to get in touch in GitHub discussions or via X/Bluesky for help or feedback.

总结

¥Wrapping up

我计划撰写一系列额外的文章,解释一些主要功能(例如 zod/v4-mini)背后的设计过程。我会在发布后更新此部分。

¥I'm planning to write up a series of additional posts explaining the design process behind some major features like zod/v4-mini. I'll update this section as those get posted.

对于库作者,现在有一个专门的 面向库作者 指南,其中描述了在 Zod 之上构建的最佳实践。它解答了关于如何同时支持 Zod 3 和 Zod 4(包括 Mini)的常见问题。

¥For library authors, there is now a dedicated For library authors guide that describes the best practices for building on top of Zod. It answers common questions about how to support Zod 3 & Zod 4 (including Mini) simultaneously.

pnpm upgrade zod@^3.25.0

祝你解析愉快!
- Colin McDonnell @colinhacks

¥Happy parsing!
— Colin McDonnell @colinhacks