リリースノート
1年にわたる活発な開発を経て、Zod 4 がついに安定版になりました!より速く、よりスリムで、tsc 効率が高く、長らく要望のあったいくつかの機能が実装されています。
Clerk に心から感謝します。彼らは非常に寛大な OSS Fellowship を通じて私の Zod 4 への取り組みを支援してくれました。(予想よりはるかに長引いた!)開発プロセス全体を通して、彼らは素晴らしいパートナーでした。
バージョニング
アップグレード方法:
破壊的変更の完全なリストについては、移行ガイド を参照してください。この記事では、新機能と強化点に焦点を当てています。
なぜ新しいメジャーバージョンなのか?
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 リポジトリで自分で実行できます:
特定のベンチマークを実行するには:
文字列解析が 14倍 高速化
配列解析が 7倍 高速化
オブジェクト解析が 6.5倍 高速化
これは Moltar validation library benchmark を実行しています。
tsc のインスタンス化が 100分の1 に削減
以下の単純なファイルを考えてみましょう:
このファイルを "zod/v3" を使用して tsc --extendedDiagnostics でコンパイルすると、25000 回以上の型のインスタンス化が発生します。一方、"zod/v4" ではわずか 〜175 回です。
Zod リポジトリには tsc ベンチマーク用のプレイグラウンドが含まれています。packages/tsc にあるコンパイラベンチマークを使用して、自分で試してみてください。正確な数字は実装の進化に伴い変更される可能性があります。
さらに重要なことに、Zod 4 では ZodObject やその他のスキーマクラスのジェネリクスを再設計し簡素化することで、厄介な「インスタンス化の爆発」を回避しています。例えば、.extend() と .omit() を繰り返しチェーンさせること——これは以前はコンパイラの問題を引き起こしていました:
Zod 3 では、これのコンパイルに 4000ms かかりました。さらに .extend() の呼び出しを追加すると、「Possibly infinite(無限の可能性)」エラーが発生していました。Zod 4 では、これは 400ms でコンパイルされ、10倍 高速です。
今後の tsgo コンパイラと組み合わせることで、Zod 4 のエディタパフォーマンスは、はるかに大規模なスキーマやコードベースにも対応できるようになるでしょう。
コアバンドルサイズが 2分の1 に削減
以下の単純なスクリプトを考えてみましょう。
バリデーションにおいては、これ以上ないほどシンプルです。これは意図的であり、コアバンドルサイズ——単純なケースでもバンドルに含まれてしまうコード——を測定するのに適した方法です。Zod 3 と Zod 4 両方を使ってこれを rollup でバンドルし、最終的なバンドルを比較します。
| Package | Bundle (gzip) |
|---|---|
| Zod 3 | 12.47kb |
| Zod 4 | 5.36kb |
Zod 4 のコアバンドルは約 57% 小さくなっています (2.3倍)。これは良いことです!しかし、もっと改善できます。
Zod Mini の紹介
Zod のメソッド重視の API は、根本的に Tree-shaking(不要コードの削除)が困難です。単純な z.boolean() スクリプトであっても、.optional() や .array() など、使用していない多くのメソッドの実装を取り込んでしまいます。実装をスリムにするだけでは限界があります。そこで Zod Mini の出番です。
これは zod と 1対1 で対応する、関数型で Tree-shaking 可能な API を持つ Zod のバリアントです。Zod がメソッドを使用する場所で、Zod Mini は一般的にラッパー関数を使用します:
すべてのメソッドがなくなったわけではありません!解析(parsing)メソッドは Zod と Zod Mini で同一です:
リファインメント(refinements)を追加するために使用される汎用の .check() メソッドもあります。
以下のトップレベルのリファインメントが Zod Mini で利用可能です。これらがどの Zod メソッドに対応するかは、説明するまでもないでしょう。
このより関数的な API により、バンドラが未使用の API を Tree-shake するのが容易になります。ほとんどのユースケースでは通常の Zod が依然として推奨されますが、通常とは異なる厳しいバンドルサイズ制約があるプロジェクトでは Zod Mini を検討すべきです。
コアバンドルサイズが 6.6倍 縮小
上記のスクリプトを "zod" の代わりに "zod/mini" を使用するように更新したものがこれです。
これを rollup でビルドすると、gzip 圧縮されたバンドルサイズは 1.88kb になります。これは zod@3 と比較してコアバンドルサイズが 85% (6.6倍) 削減されたことになります。
| Package | Bundle (gzip) |
|---|---|
| Zod 3 | 12.47kb |
| Zod 4 (通常版) | 5.36kb |
| Zod 4 (mini) | 1.88kb |
詳細は専用の zod/mini ドキュメントページをご覧ください。完全な API の詳細は既存のドキュメントページに混在しており、API が異なる箇所ではコードブロックに "Zod" と "Zod Mini" の個別のタブが含まれています。
メタデータ
Zod 4 では、スキーマに型安全なメタデータを追加するための新しいシステムが導入されました。メタデータはスキーマ自体の内部には保存されません。代わりに、スキーマと型付きメタデータを関連付ける「スキーマレジストリ」に保存されます。z.registry() でレジストリを作成します:
レジストリにスキーマを追加するには:
あるいは、利便性のためにスキーマ上の .register() メソッドを使用することもできます:
グローバルレジストリ
Zod はグローバルレジストリ z.globalRegistry もエクスポートしており、一般的な JSON Schema 互換のメタデータを受け入れます。
.meta()
スキーマを z.globalRegistry に便利に追加するには、 .meta() メソッドを使用します。
Zod 3 との互換性のために .describe() も引き続き利用可能ですが、.meta() が推奨されます。
JSON Schema 変換
Zod 4 では、z.toJSONSchema() を介してファーストパーティでの JSON Schema 変換が導入されました。
z.globalRegistry 内のメタデータはすべて、自動的に JSON Schema の出力に含まれます。
生成される JSON Schema のカスタマイズに関する情報については、JSON Schema ドキュメント を参照してください。
再帰的オブジェクト
これは予想外の収穫でした。この問題を解決しようと何年も試みた末、ついに Zod で再帰的なオブジェクト型を適切に推論する 方法を見つけました。再帰的な型を定義するには:
相互再帰的な型 も表現できます:
Zod 3 の再帰型のパターンとは異なり、型キャストは必要ありません。結果として得られるスキーマは通常の ZodObject インスタンスであり、メソッドのフルセットを利用可能です。
File スキーマ
File インスタンスを検証するには:
国際化 (i18n)
Zod 4 では、エラーメッセージを異なる言語にグローバルに翻訳するための新しい locales API が導入されました。
サポートされているロケールの完全なリストについては、エラーのカスタマイズ を参照してください。新しい言語が利用可能になると、このセクションは常に更新されます。
エラーの整形表示 (pretty-printing)
zod-validation-error パッケージの人気は、エラーを整形表示する公式 API への大きな需要があることを示しています。現在そのパッケージを使用している場合は、ぜひ使い続けてください。
Zod は ZodError をユーザーフレンドリーなフォーマット済み文字列に変換するためのトップレベル関数 z.prettifyError を実装しました。
これは、以下のような整形表示可能な複数行の文字列を返します:
現在、フォーマットは設定変更できませんが、将来的には変更される可能性があります。
トップレベルの文字列フォーマット
すべての「文字列フォーマット」(email など)は z モジュールのトップレベル関数に昇格しました。これはより簡潔であり、Tree-shake もより容易です。メソッド版(z.string().email() など)も引き続き利用可能ですが、非推奨となっています。これらは次のメジャーバージョンで削除される予定です。
カスタム Email 正規表現
z.email() API は、カスタム正規表現をサポートするようになりました。正規の email 正規表現というものは1つではありません。アプリケーションによって、より厳密にするか、より緩やかにするかを選択する場合があります。利便性のために、Zod はいくつかの一般的なものをエクスポートしています。
テンプレートリテラル型
Zod 4 は z.templateLiteral() を実装しました。テンプレートリテラル型は、おそらく TypeScript の型システムの中で、これまで表現できなかった最大の機能でしょう。
文字列化可能なすべての Zod スキーマタイプは、内部正規表現を保持しています:文字列、z.email() のような文字列フォーマット、数値、ブール値、bigint、enum、リテラル、undefined/optional、null/nullable、その他のテンプレートリテラルなどです。z.templateLiteral コンストラクタはこれらを連結してスーパー正規表現にするため、文字列フォーマット(z.email())のようなものは適切に強制されます(ただし、カスタムリファインメントは強制されません!)。
詳細は テンプレートリテラルのドキュメント を参照してください。
数値フォーマット
固定幅の整数および浮動小数点を表すための新しい数値「フォーマット」が追加されました。これらは、適切な最小/最大制約が既に追加された ZodNumber インスタンスを返します。
同様に、以下の bigint 数値フォーマットも追加されました。これらの整数型は JavaScript の number で安全に表現できる範囲を超えているため、これらは適切な最小/最大制約が既に追加された ZodBigInt インスタンスを返します。
Stringbool
既存の z.coerce.boolean() API は非常にシンプルです。偽値 (false, undefined, null, 0, "", NaN 等) は false になり、真値は true になります。
これは依然として良い API であり、その動作は他の z.coerce API と一致しています。しかし、一部のユーザーは、より洗練された「環境変数スタイル」のブール値強制変換を求めていました。これをサポートするために、Zod 4 は z.stringbool() を導入しました:
真値(truthy)と偽値(falsy)をカスタマイズするには:
詳細は z.stringbool() ドキュメント を参照してください。
エラーカスタマイズの簡素化
Zod 4 における破壊的変更の大半は、エラーカスタマイズ API に関連しています。これらは Zod 3 では少し混乱していましたが、Zod 4 では非常にエレガントになったため、ここで強調する価値があると思います。
手短に言うと、以下の API を置き換える形で、エラーをカスタマイズするための単一の統一された error パラメータが登場しました:
message を error に置き換えます。(message パラメータはまだサポートされていますが、非推奨です。)
invalid_type_error と required_error を error(関数構文)に置き換えます:
errorMap を error(関数構文)に置き換えます:
アップグレードされた z.discriminatedUnion()
判別可能なユニオン(Discriminated unions)は、ユニオンやパイプを含む、以前はサポートされていなかった多くのスキーマタイプをサポートするようになりました:
おそらく最も重要なのは、判別可能なユニオンが 合成(compose) できるようになったことです。つまり、ある判別可能なユニオンを別のユニオンのメンバーとして使用できます。
z.literal() での複数の値
z.literal() API は、オプションで複数の値をサポートするようになりました。
リファインメントはスキーマ内部に存在
Zod 3 では、それらは元のスキーマをラップする ZodEffects クラスに保存されていました。これは不便であり、.refine() を .min() のような他のスキーマメソッドと交互に使用できないことを意味していました。
Zod 4 では、リファインメントはスキーマ自体の中に保存されるため、上記のコードは期待通りに動作します。
.overwrite()
.transform() メソッドは非常に便利ですが、大きな欠点が1つあります。それは、出力タイプが実行時に イントロスペクション(内省)可能 でなくなることです。変換関数はブラックボックスであり、何でも返す可能性があります。これは(とりわけ)、スキーマを JSON Schema に変換する健全な方法がないことを意味します。
Zod 4 は、推論される型を変更しない 変換を表すための新しい .overwrite() メソッドを導入しました。.transform() とは異なり、このメソッドは元のクラスのインスタンスを返します。上書き関数はリファインメントとして保存されるため、推論される型を変更しません(また、できません)。
既存の .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 を含む)を同時にサポートする方法に関する一般的な質問に答えています。
Happy parsing!
— Colin McDonnell @colinhacks

