💎 Zod 4 is now stable!  Read the announcement.
Zod logo

リリースノート

1年にわたる活発な開発を経て、Zod 4 がついに安定版になりました!より速く、よりスリムで、tsc 効率が高く、長らく要望のあったいくつかの機能が実装されています。

❤️

Clerk に心から感謝します。彼らは非常に寛大な OSS Fellowship を通じて私の Zod 4 への取り組みを支援してくれました。(予想よりはるかに長引いた!)開発プロセス全体を通して、彼らは素晴らしいパートナーでした。

バージョニング

アップグレード方法:

npm install zod@^4.0.0

破壊的変更の完全なリストについては、移行ガイド を参照してください。この記事では、新機能と強化点に焦点を当てています。

なぜ新しいメジャーバージョンなのか?

Zod v3.0 は2021年5月にリリースされました(!)。当時、Zod は GitHub で 2700 スター、週間ダウンロード数は 60 万でした。今日では 37.8k スター、週間ダウンロード数は 3100 万に達しています(6週間前にベータ版が出たときの 2300 万から増加!)。24回のマイナーバージョンを経て、Zod 3 のコードベースは限界に達していました。最も要望の多い機能や改善には、破壊的変更が必要でした。

Zod 4 は、Zod 3 の長年の設計上の制限の多くを一挙に解決し、いくつかの長年の要望機能とパフォーマンスの大幅な向上への道を開きました。Zod の 最も投票数の多い未解決の問題トップ10 のうち9つを解決しています。運が良ければ、これは今後数年間の新しい基盤となるでしょう。

新機能の概要については、目次をご覧ください。項目をクリックするとそのセクションにジャンプします。

ベンチマーク

これらのベンチマークは Zod リポジトリで自分で実行できます:

$ git clone [email protected]: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

配列解析が 7倍 高速化

$ 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 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分の1 に削減

以下の単純なファイルを考えてみましょう:

import * as z from "zod";
 
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 やその他のスキーマクラスのジェネリクスを再設計し簡素化することで、厄介な「インスタンス化の爆発」を回避しています。例えば、.extend().omit() を繰り返しチェーンさせること——これは以前はコンパイラの問題を引き起こしていました:

import * as z from "zod";
 
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() の呼び出しを追加すると、「Possibly infinite(無限の可能性)」エラーが発生していました。Zod 4 では、これは 400ms でコンパイルされ、10倍 高速です。

今後の tsgo コンパイラと組み合わせることで、Zod 4 のエディタパフォーマンスは、はるかに大規模なスキーマやコードベースにも対応できるようになるでしょう。

コアバンドルサイズが 2分の1 に削減

以下の単純なスクリプトを考えてみましょう。

import * as z from "zod";
 
const schema = z.boolean();
 
schema.parse(true);

バリデーションにおいては、これ以上ないほどシンプルです。これは意図的であり、コアバンドルサイズ——単純なケースでもバンドルに含まれてしまうコード——を測定するのに適した方法です。Zod 3 と Zod 4 両方を使ってこれを rollup でバンドルし、最終的なバンドルを比較します。

PackageBundle (gzip)
Zod 312.47kb
Zod 45.36kb

Zod 4 のコアバンドルは約 57% 小さくなっています (2.3倍)。これは良いことです!しかし、もっと改善できます。

Zod Mini の紹介

Zod のメソッド重視の API は、根本的に Tree-shaking(不要コードの削除)が困難です。単純な z.boolean() スクリプトであっても、.optional().array() など、使用していない多くのメソッドの実装を取り込んでしまいます。実装をスリムにするだけでは限界があります。そこで Zod Mini の出番です。

npm install zod@^4.0.0

これは zod と 1対1 で対応する、関数型で Tree-shaking 可能な API を持つ Zod のバリアントです。Zod がメソッドを使用する場所で、Zod Mini は一般的にラッパー関数を使用します:

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

すべてのメソッドがなくなったわけではありません!解析(parsing)メソッドは Zod と Zod Mini で同一です:

import * as z from "zod/mini";
 
z.string().parse("asdf");
z.string().safeParse("asdf");
await z.string().parseAsync("asdf");
await z.string().safeParseAsync("asdf");

リファインメント(refinements)を追加するために使用される汎用の .check() メソッドもあります。

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

以下のトップレベルのリファインメントが Zod Mini で利用可能です。これらがどの Zod メソッドに対応するかは、説明するまでもないでしょう。

import * as z from "zod/mini";
 
// カスタムチェック
z.refine();
 
// ファーストクラスのチェック
z.lt(value);
z.lte(value); // エイリアス: z.maximum()
z.gt(value);
z.gte(value); // エイリアス: 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); // オブジェクトスキーマ用; `input[key]` を `schema` に対してチェック
z.mime(value); // ファイルスキーマ用 (後述)
 
// 上書き (これらは推論される型を変更*しません*!)
z.overwrite(value => newValue);
z.normalize();
z.trim();
z.toLowerCase();
z.toUpperCase();

このより関数的な API により、バンドラが未使用の API を Tree-shake するのが容易になります。ほとんどのユースケースでは通常の Zod が依然として推奨されますが、通常とは異なる厳しいバンドルサイズ制約があるプロジェクトでは Zod Mini を検討すべきです。

コアバンドルサイズが 6.6倍 縮小

上記のスクリプトを "zod" の代わりに "zod/mini" を使用するように更新したものがこれです。

import * as z from "zod/mini";
 
const schema = z.boolean();
schema.parse(false);

これを rollup でビルドすると、gzip 圧縮されたバンドルサイズは 1.88kb になります。これは zod@3 と比較してコアバンドルサイズが 85% (6.6倍) 削減されたことになります。

PackageBundle (gzip)
Zod 312.47kb
Zod 4 (通常版)5.36kb
Zod 4 (mini)1.88kb

詳細は専用の zod/mini ドキュメントページをご覧ください。完全な API の詳細は既存のドキュメントページに混在しており、API が異なる箇所ではコードブロックに "Zod""Zod Mini" の個別のタブが含まれています。

メタデータ

Zod 4 では、スキーマに型安全なメタデータを追加するための新しいシステムが導入されました。メタデータはスキーマ自体の内部には保存されません。代わりに、スキーマと型付きメタデータを関連付ける「スキーマレジストリ」に保存されます。z.registry() でレジストリを作成します:

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

レジストリにスキーマを追加するには:

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

あるいは、利便性のためにスキーマ上の .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: ["[email protected]"],
  extraKey: "Additional properties are also allowed"
});

.meta()

スキーマを z.globalRegistry に便利に追加するには、 .meta() メソッドを使用します。

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

Zod 3 との互換性のために .describe() も引き続き利用可能ですが、.meta() が推奨されます。

z.string().describe("An email address");
 
// 以下と同等
z.string().meta({ description: "An email address" });

JSON Schema 変換

Zod 4 では、z.toJSONSchema() を介してファーストパーティでの JSON Schema 変換が導入されました。

import * as z from "zod";
 
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 ドキュメント を参照してください。

再帰的オブジェクト

これは予想外の収穫でした。この問題を解決しようと何年も試みた末、ついに Zod で再帰的なオブジェクト型を適切に推論する 方法を見つけました。再帰的な型を定義するには:

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

相互再帰的な型 も表現できます:

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 の再帰型のパターンとは異なり、型キャストは必要ありません。結果として得られるスキーマは通常の ZodObject インスタンスであり、メソッドのフルセットを利用可能です。

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

File スキーマ

File インスタンスを検証するには:

const fileSchema = z.file();
 
fileSchema.min(10_000); // 最小 .size (バイト)
fileSchema.max(1_000_000); // 最大 .size (バイト)
fileSchema.mime(["image/png"]); // MIME タイプ

国際化 (i18n)

Zod 4 では、エラーメッセージを異なる言語にグローバルに翻訳するための新しい locales API が導入されました。

import * as z from "zod";
 
// 英語ロケールを設定 (デフォルト)
z.config(z.locales.en());

サポートされているロケールの完全なリストについては、エラーのカスタマイズ を参照してください。新しい言語が利用可能になると、このセクションは常に更新されます。

エラーの整形表示 (pretty-printing)

zod-validation-error パッケージの人気は、エラーを整形表示する公式 API への大きな需要があることを示しています。現在そのパッケージを使用している場合は、ぜひ使い続けてください。

Zod は ZodError をユーザーフレンドリーなフォーマット済み文字列に変換するためのトップレベル関数 z.prettifyError を実装しました。

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-shake もより容易です。メソッド版(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.lowercase();
z.iso.date();
z.iso.datetime();
z.iso.duration();
z.iso.time();

カスタム Email 正規表現

z.email() API は、カスタム正規表現をサポートするようになりました。正規の email 正規表現というものは1つではありません。アプリケーションによって、より厳密にするか、より緩やかにするかを選択する場合があります。利便性のために、Zod はいくつかの一般的なものをエクスポートしています。

// Zod のデフォルト email 正規表現 (Gmail ルール)
// 参照: 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 を許可する緩やかな正規表現 (国際的な 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}` (min/max リファインメントが強制されます!)

文字列化可能なすべての Zod スキーマタイプは、内部正規表現を保持しています:文字列、z.email() のような文字列フォーマット、数値、ブール値、bigint、enum、リテラル、undefined/optional、null/nullable、その他のテンプレートリテラルなどです。z.templateLiteral コンストラクタはこれらを連結してスーパー正規表現にするため、文字列フォーマット(z.email())のようなものは適切に強制されます(ただし、カスタムリファインメントは強制されません!)。

詳細は テンプレートリテラルのドキュメント を参照してください。

数値フォーマット

固定幅の整数および浮動小数点を表すための新しい数値「フォーマット」が追加されました。これらは、適切な最小/最大制約が既に追加された 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 は非常にシンプルです。偽値 (false, undefined, null, 0, "", NaN 等) は false になり、真値は true になります。

これは依然として良い API であり、その動作は他の z.coerce API と一致しています。しかし、一部のユーザーは、より洗練された「環境変数スタイル」のブール値強制変換を求めていました。これをサポートするために、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("enabled")      // => 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(/* その他 */); // ZodError<[{ code: "invalid_value" }]>

真値(truthy)と偽値(falsy)をカスタマイズするには:

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

詳細は z.stringbool() ドキュメント を参照してください。

エラーカスタマイズの簡素化

Zod 4 における破壊的変更の大半は、エラーカスタマイズ API に関連しています。これらは Zod 3 では少し混乱していましたが、Zod 4 では非常にエレガントになったため、ここで強調する価値があると思います。

手短に言うと、以下の API を置き換える形で、エラーをカスタマイズするための単一の統一された error パラメータが登場しました:

messageerror に置き換えます。(message パラメータはまだサポートされていますが、非推奨です。)

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

invalid_type_errorrequired_errorerror(関数構文)に置き換えます:

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

errorMaperror(関数構文)に置き換えます:

// 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)は、ユニオンやパイプを含む、以前はサポートされていなかった多くのスキーマタイプをサポートするようになりました:

const MyResult = z.discriminatedUnion("status", [
  // 単純なリテラル
  z.object({ status: z.literal("aaa"), data: z.string() }),
  // ユニオン判別子
  z.object({ status: z.union([z.literal("bbb"), z.literal("ccc")]) }),
  // パイプ判別子
  z.object({ status: z.literal("fail").transform(val => val.toUpperCase()) }),
]);

おそらく最も重要なのは、判別可能なユニオンが 合成(compose) できるようになったことです。つまり、ある判別可能なユニオンを別のユニオンのメンバーとして使用できます。

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

z.literal() での複数の値

z.literal() API は、オプションで複数の値をサポートするようになりました。

const httpCodes = z.literal([ 200, 201, 202, 204, 206, 207, 208, 226 ]);
 
// 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)
]);

リファインメントはスキーマ内部に存在

Zod 3 では、それらは元のスキーマをラップする ZodEffects クラスに保存されていました。これは不便であり、.refine().min() のような他のスキーマメソッドと交互に使用できないことを意味していました。

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

Zod 4 では、リファインメントはスキーマ自体の中に保存されるため、上記のコードは期待通りに動作します。

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

.overwrite()

.transform() メソッドは非常に便利ですが、大きな欠点が1つあります。それは、出力タイプが実行時に イントロスペクション(内省)可能 でなくなることです。変換関数はブラックボックスであり、何でも返す可能性があります。これは(とりわけ)、スキーマを JSON Schema に変換する健全な方法がないことを意味します。

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

Zod 4 は、推論される型を変更しない 変換を表すための新しい .overwrite() メソッドを導入しました。.transform() とは異なり、このメソッドは元のクラスのインスタンスを返します。上書き関数はリファインメントとして保存されるため、推論される型を変更しません(また、できません)。

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

既存の .trim().toLowerCase().toUpperCase() メソッドは .overwrite() を使用して再実装されました。

拡張可能な基盤:zod/v4/core

これは大多数の Zod ユーザーには関係ありませんが、強調しておく価値があります。Zod Mini の追加により、Zod と Zod Mini 間で共有されるコア機能を含む共有サブパッケージ zod/v4/core を作成する必要がありました。

最初はこれに抵抗がありましたが、今では Zod 4 の最も重要な機能の1つだと考えています。これにより、Zod は単なるライブラリから、他のライブラリに組み込むことができる高速なバリデーション「基質(substrate)」へとレベルアップしました。

スキーマライブラリを構築している場合は、Zod および Zod Mini の実装を参照して、zod/v4/core が提供する基盤の上にどのように構築するかを確認してください。ヘルプやフィードバックが必要な場合は、GitHub ディスカッションや X/Bluesky でお気軽にご連絡ください。

締めくくり

Zod Mini のような主要な機能の背後にある設計プロセスを説明する一連の追加記事を書く予定です。それらが投稿され次第、このセクションを更新します。

ライブラリ作成者向けに、Zod の上に構築するためのベストプラクティスを説明した専用の ライブラリ作成者向け ガイドができました。Zod 3 と Zod 4(Mini を含む)を同時にサポートする方法に関する一般的な質問に答えています。

pnpm upgrade zod@latest

Happy parsing!
— Colin McDonnell @colinhacks