zod4

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Zod 4 Expert Guide

Zod 4 专家指南

Zod 4 is a major release with significant performance improvements, reduced TypeScript compilation times, and a cleaner API. This skill covers migration from v3 and idiomatic Zod 4 usage.
Zod 4 是一个重大版本,带来了显著的性能提升、更短的 TypeScript 编译时间以及更简洁的 API。本指南涵盖从 v3 迁移的方法和 Zod 4 的惯用用法。

Quick Migration Checklist

快速迁移清单

Before diving deep, address these high-impact breaking changes:
ChangeZod 3Zod 4
Record schemas
z.record(z.string())
z.record(z.string(), z.string())
Strict objects
.strict()
z.strictObject({...})
Passthrough
.passthrough()
z.looseObject({...})
Error formatting
err.format()
z.treeifyError(err)
Coerce input type
string
unknown
Install Zod 4:
bash
npm install zod@^4.0.0
For detailed breaking changes, see ./reference/breaking-changes.md.

在深入了解之前,请先处理这些影响重大的破坏性变更:
变更Zod 3Zod 4
Record 模式
z.record(z.string())
z.record(z.string(), z.string())
严格对象
.strict()
z.strictObject({...})
透传未知键
.passthrough()
z.looseObject({...})
错误格式化
err.format()
z.treeifyError(err)
强制转换输入类型
string
unknown
安装 Zod 4:
bash
npm install zod@^4.0.0
如需了解详细的破坏性变更,请查看 ./reference/breaking-changes.md

Key Breaking Changes

主要破坏性变更

1. z.record() Requires Two Arguments

1. z.record() 需要两个参数

typescript
// Zod 3 (BROKEN in v4)
z.record(z.string());

// Zod 4 (REQUIRED)
z.record(z.string(), z.string());
typescript
// Zod 3(在 v4 中失效)
z.record(z.string());

// Zod 4(必须写法)
z.record(z.string(), z.string());

2. Strict/Loose Object Syntax

2. 严格/宽松对象语法

typescript
// Zod 3
z.object({ name: z.string() }).strict();
z.object({ name: z.string() }).passthrough();

// Zod 4
z.strictObject({ name: z.string() });
z.looseObject({ name: z.string() });
typescript
// Zod 3
z.object({ name: z.string() }).strict();
z.object({ name: z.string() }).passthrough();

// Zod 4
z.strictObject({ name: z.string() });
z.looseObject({ name: z.string() });

3. .default() Behavior Changed

3. .default() 行为变更

In Zod 4,
.default()
short-circuits if input is
undefined
and returns the default directly (without parsing). Use
.prefault()
for the old behavior:
typescript
// Zod 4: default must match OUTPUT type
const schema = z.string()
  .transform(val => val.length)
  .default(0);  // Returns 0 directly, not parsed

// To parse the default (old behavior):
const schema = z.string()
  .transform(val => val.length)
  .prefault("tuna");  // "tuna" is parsed → 4
在 Zod 4 中,若输入为
undefined
.default()
会直接返回默认值(不经过解析)。如需旧版行为,请使用
.prefault()
typescript
// Zod 4:默认值必须匹配输出类型
const schema = z.string()
  .transform(val => val.length)
  .default(0);  // 直接返回 0,不经过解析

// 如需解析默认值(旧版行为):
const schema = z.string()
  .transform(val => val.length)
  .prefault("tuna");  // "tuna" 会被解析 → 4

4. Error Handling Changes

4. 错误处理变更

typescript
// Zod 3
const formatted = err.format();
const flat = err.flatten();

// Zod 4
const tree = z.treeifyError(err);

// Adding issues
err.issues.push({ /* new issue */ });
typescript
// Zod 3
const formatted = err.format();
const flat = err.flatten();

// Zod 4
const tree = z.treeifyError(err);

// 添加错误信息
err.issues.push({ /* 新错误信息 */ });

5. z.coerce Input Type

5. z.coerce 输入类型

typescript
const schema = z.coerce.string();
type Input = z.input<typeof schema>;
// Zod 3: string
// Zod 4: unknown

typescript
const schema = z.coerce.string();
type Input = z.input<typeof schema>;
// Zod 3: string
// Zod 4: unknown

New Features

新特性

z.file() - File Validation

z.file() - 文件验证

typescript
const fileSchema = z.file()
  .min(10_000)        // minimum bytes
  .max(1_000_000)     // maximum bytes
  .mime(["image/png", "image/jpeg"]);
typescript
const fileSchema = z.file()
  .min(10_000)        // 最小字节数
  .max(1_000_000)     // 最大字节数
  .mime(["image/png", "image/jpeg"]);

z.templateLiteral() - Template Literal Types

z.templateLiteral() - 模板字面量类型

typescript
const css = z.templateLiteral([z.number(), z.enum(["px", "em", "rem"])]);
// `${number}px` | `${number}em` | `${number}rem`

const email = z.templateLiteral([
  z.string().min(1),
  "@",
  z.string().max(64),
]);
typescript
const css = z.templateLiteral([z.number(), z.enum(["px", "em", "rem"])]);
// `${number}px` | `${number}em` | `${number}rem`

const email = z.templateLiteral([
  z.string().min(1),
  "@",
  z.string().max(64),
]);

.meta() - Schema Metadata

.meta() - 模式元数据

typescript
z.string().meta({
  id: "email_address",
  title: "Email address",
  description: "User's email",
  examples: ["user@example.com"]
});
typescript
z.string().meta({
  id: "email_address",
  title: "Email address",
  description: "User's email",
  examples: ["user@example.com"]
});

z.globalRegistry - Global Schema Registry

z.globalRegistry - 全局模式注册表

typescript
z.globalRegistry.add(mySchema, {
  id: "user_schema",
  title: "User",
  description: "User data structure"
});
typescript
z.globalRegistry.add(mySchema, {
  id: "user_schema",
  title: "User",
  description: "User data structure"
});

z.locales - Internationalization

z.locales - 国际化

typescript
import { z } from "zod";
import { en } from "zod/locales/en";

z.config(z.locales.en());  // Configure error messages
typescript
import { z } from "zod";
import { en } from "zod/locales/en";

z.config(z.locales.en());  // 配置错误提示信息

z.strictObject() / z.looseObject()

z.strictObject() / z.looseObject()

typescript
// Rejects unknown keys
z.strictObject({ name: z.string() });

// Allows unknown keys (passthrough)
z.looseObject({ name: z.string() });
For complete new features guide, see ./reference/new-features.md.

typescript
// 拒绝未知键
z.strictObject({ name: z.string() });

// 允许未知键(透传)
z.looseObject({ name: z.string() });
如需完整的新特性指南,请查看 ./reference/new-features.md

Zod Mini

Zod Mini

Zod Mini (
zod/mini
) provides a smaller bundle with tree-shakable, functional API:
typescript
import * as z from "zod/mini";

// Functional checks instead of methods
const schema = z.pipe(
  z.string(),
  z.minLength(1),
  z.maxLength(100),
  z.regex(/^[a-z]+$/)
);

// Available functions
z.lt(value);
z.gt(value);
z.positive();
z.negative();
z.minLength(value);
z.maxLength(value);
z.regex(pattern);
z.trim();
z.toLowerCase();
z.toUpperCase();

Zod Mini (
zod/mini
) 提供了一个体积更小的包,支持 tree-shaking,采用函数式 API:
typescript
import * as z from "zod/mini";

// 使用函数式检查而非链式方法
const schema = z.pipe(
  z.string(),
  z.minLength(1),
  z.maxLength(100),
  z.regex(/^[a-z]+$/)
);

// 可用函数
z.lt(value);
z.gt(value);
z.positive();
z.negative();
z.minLength(value);
z.maxLength(value);
z.regex(pattern);
z.trim();
z.toLowerCase();
z.toUpperCase();

Migration Patterns

迁移模式

Pattern 1: Update z.record() Calls

模式 1:更新 z.record() 调用

Search and replace:
typescript
// Find
z.record(valueSchema)

// Replace with
z.record(z.string(), valueSchema)
搜索并替换:
typescript
// 查找
z.record(valueSchema)

// 替换为
z.record(z.string(), valueSchema)

Pattern 2: Update Strict Objects

模式 2:更新严格对象

typescript
// Find
z.object({...}).strict()

// Replace with
z.strictObject({...})
typescript
// 查找
z.object({...}).strict()

// 替换为
z.strictObject({...})

Pattern 3: Update Error Handling

模式 3:更新错误处理

typescript
// Find
try {
  schema.parse(data);
} catch (err) {
  if (err instanceof z.ZodError) {
    const formatted = err.format();
  }
}

// Replace with
try {
  schema.parse(data);
} catch (err) {
  if (err instanceof z.ZodError) {
    const tree = z.treeifyError(err);
  }
}
typescript
// 查找
try {
  schema.parse(data);
} catch (err) {
  if (err instanceof z.ZodError) {
    const formatted = err.format();
  }
}

// 替换为
try {
  schema.parse(data);
} catch (err) {
  if (err instanceof z.ZodError) {
    const tree = z.treeifyError(err);
  }
}

Pattern 4: Fix Default Values

模式 4:修复默认值

If using
.default()
with transforms, check if default matches output type:
typescript
// If this breaks:
z.string().transform(s => s.length).default("hello")

// Change to:
z.string().transform(s => s.length).prefault("hello")
// OR
z.string().transform(s => s.length).default(5)  // Match output type
For complete migration checklist, see ./reference/migration-checklist.md.

如果在转换中使用
.default()
,请检查默认值是否匹配输出类型:
typescript
// 如果此代码失效:
z.string().transform(s => s.length).default("hello")

// 修改为:
z.string().transform(s => s.length).prefault("hello")
// 或者
z.string().transform(s => s.length).default(5)  // 匹配输出类型
如需完整的迁移清单,请查看 ./reference/migration-checklist.md

Common Issues

常见问题

ErrorCauseFix
Expected 2 arguments, got 1
z.record()
single arg
Add key schema:
z.record(z.string(), ...)
Property 'strict' does not exist
.strict()
removed
Use
z.strictObject()
Property 'format' does not exist
.format()
removed
Use
z.treeifyError(err)
Type mismatch on
.default()
Default must match outputUse
.prefault()
or fix default type

错误原因修复方案
Expected 2 arguments, got 1
z.record()
仅传了一个参数
添加键模式:
z.record(z.string(), ...)
Property 'strict' does not exist
.strict()
已被移除
使用
z.strictObject()
Property 'format' does not exist
.format()
已被移除
使用
z.treeifyError(err)
.default()
类型不匹配
默认值必须匹配输出类型使用
.prefault()
或修正默认值类型

Codemod

Codemod

A community-maintained codemod is available:
bash
npx zod-v3-to-v4
This automates many of the breaking change fixes.

社区维护的 codemod 可用:
bash
npx zod-v3-to-v4
这将自动修复许多破坏性变更问题。

Resources

资源