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

移行ガイド

この移行ガイドは、Zod 4 における破壊的変更(breaking changes)を影響の大きい順にリストアップすることを目的としています。Zod 4 のパフォーマンス向上や新機能について詳しくは、紹介記事をご覧ください。

npm install zod@^4.0.0

Zod の多くの動作と API が、より直感的で、一貫性のあるものになりました。本書で説明する破壊的変更は、多くの場合、Zod ユーザーにとって生活の質(QoL)の大幅な向上を意味します。このガイドをよく読むことを強くお勧めします。

注意 — Zod 3 では、パブリック API の一部とは見なされない、文書化されていない準内部的なユーティリティタイプや関数がいくつかエクスポートされていました。それらへの変更はここでは文書化されていません。

非公式 codemod — コミュニティによってメンテナンスされている codemod zod-v3-to-v4 が利用可能です。

エラーのカスタマイズ (Error customization)

Zod 4 では、エラーカスタマイズのための API を単一の error パラメータに統一しました。以前の Zod のエラーカスタマイズ API は断片化されており、一貫性がありませんでした。これは Zod 4 で整理されました。

message の非推奨

messageerror に置き換えられます。message パラメータは引き続きサポートされますが、非推奨となります。

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

invalid_type_errorrequired_error の削除

invalid_type_error / required_error パラメータは削除されました。これらは数年前、errorMap よりも冗長でない方法でエラーをカスタマイズするために急遽追加されたものでした。これらにはあらゆる種類の落とし穴があり(errorMap と併用できないなど)、Zod の実際の issue コードとも一致していません(required issue コードは存在しません)。

これらは新しい error パラメータできれいに表現できるようになりました。

z.string({ 
  error: (issue) => issue.input === undefined 
    ? "This field is required" 
    : "Not a string" 
});

errorMap の削除

これは error にリネームされました。

エラーマップ(Error maps)は、プレーンな string を返すことができるようになりました({message: string} の代わりに)。また、undefined を返すことで、チェーン内の次のエラーマップに制御を譲ることを Zod に伝えることもできます。

z.string().min(5, {
  error: (issue) => {
    if (issue.code === "too_small") {
      return `Value must be >${issue.minimum}`
    }
  },
});

ZodError

issue フォーマットの更新

issue フォーマットが大幅に合理化されました。

import * as z from "zod"; // v4
 
type IssueFormats = 
  | z.core.$ZodIssueInvalidType
  | z.core.$ZodIssueTooBig
  | z.core.$ZodIssueTooSmall
  | z.core.$ZodIssueInvalidStringFormat
  | z.core.$ZodIssueNotMultipleOf
  | z.core.$ZodIssueUnrecognizedKeys
  | z.core.$ZodIssueInvalidValue
  | z.core.$ZodIssueInvalidUnion
  | z.core.$ZodIssueInvalidKey // 新規: z.record/z.map で使用 
  | z.core.$ZodIssueInvalidElement // 新規: z.map/z.set で使用
  | z.core.$ZodIssueCustom;

以下は、Zod 3 の issue タイプとそれに対応する Zod 4 のリストです:

import * as z from "zod"; // v3
 
export type IssueFormats =
  | z.ZodInvalidTypeIssue // ♻️ z.core.$ZodIssueInvalidType にリネーム
  | z.ZodTooBigIssue  // ♻️ z.core.$ZodIssueTooBig にリネーム
  | z.ZodTooSmallIssue // ♻️ z.core.$ZodIssueTooSmall にリネーム
  | z.ZodInvalidStringIssue // ♻️ z.core.$ZodIssueInvalidStringFormat
  | z.ZodNotMultipleOfIssue // ♻️ z.core.$ZodIssueNotMultipleOf にリネーム
  | z.ZodUnrecognizedKeysIssue // ♻️ z.core.$ZodIssueUnrecognizedKeys にリネーム
  | z.ZodInvalidUnionIssue // ♻️ z.core.$ZodIssueInvalidUnion にリネーム
  | z.ZodCustomIssue // ♻️ z.core.$ZodIssueCustom にリネーム
  | z.ZodInvalidEnumValueIssue // ❌ z.core.$ZodIssueInvalidValue に統合
  | z.ZodInvalidLiteralIssue // ❌ z.core.$ZodIssueInvalidValue に統合
  | z.ZodInvalidUnionDiscriminatorIssue // ❌ スキーマ作成時にエラーをスロー
  | z.ZodInvalidArgumentsIssue // ❌ z.function が直接 ZodError をスロー
  | z.ZodInvalidReturnTypeIssue // ❌ z.function が直接 ZodError をスロー
  | z.ZodInvalidDateIssue // ❌ invalid_type に統合
  | z.ZodInvalidIntersectionTypesIssue // ❌ 削除 (通常の Error をスロー)
  | z.ZodNotFiniteIssue // ❌ 無限値は受け付けられなくなりました (invalid_type)

一部の Zod 4 issue タイプは統合、削除、または変更されましたが、各 issue は構造的には Zod 3 の対応するものと類似しています(ほとんどの場合、同一です)。すべての issue は依然として Zod 3 と同じ基本インターフェースに準拠しているため、最も一般的なエラー処理ロジックは変更なしで機能します。

export interface $ZodIssueBase {
  readonly code?: string;
  readonly input?: unknown;
  readonly path: PropertyKey[];
  readonly message: string;
}

エラーマップ優先度の変更

エラーマップ(error map)の優先順位が変更され、より一貫性が増しました。具体的には、.parse() に渡されたエラーマップは、スキーマレベルのエラーマップよりも 優先されなくなりました

const mySchema = z.string({ error: () => "Schema-level error" });
 
// Zod 3
mySchema.parse(12, { error: () => "Contextual error" }); // => "Contextual error"
 
// Zod 4
mySchema.parse(12, { error: () => "Contextual error" }); // => "Schema-level error"

.format() の非推奨

ZodError.format() メソッドは非推奨となりました。代わりに、トップレベルの z.treeifyError() 関数を使用してください。詳細については エラーのフォーマット を参照してください。

.flatten() の非推奨

ZodError.flatten() メソッドも非推奨となりました。代わりに、トップレベルの z.treeifyError() 関数を使用してください。詳細については エラーのフォーマット を参照してください。

.formErrors の削除

この API は .flatten() と同一でした。歴史的な理由で存在していましたが、文書化されていませんでした。

.addIssue().addIssues() の非推奨

必要な場合は、err.issues 配列に直接 push してください。

myError.issues.push({ 
  // new issue
});

z.number()

無限値なし

POSITIVE_INFINITYNEGATIVE_INFINITYz.number() の有効な値とは見なされなくなりました。

.safe() は浮動小数点数(floats)を受け付けなくなりました

Zod 3 では、z.number().safe() は非推奨です。これは .int() と同一の動作になります(下記参照)。重要なのは、浮動小数点数を受け入れなくなるということです。

.int() は安全な整数のみを受け入れます

z.number().int() API は、安全でない整数(Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER の範囲外)を受け入れなくなりました。この範囲外の整数を使用すると、自発的な丸め誤差が発生します。(また:z.int() に切り替えるべきです。)

z.string() の更新

.email() などの非推奨

文字列形式は、単純な内部リファインメント(refinement)ではなく、ZodStringサブクラス として表現されるようになりました。そのため、これらの API はトップレベルの z 名前空間に移動しました。トップレベルの API はより簡潔で、tree-shaking も容易です。

z.email();
z.uuid();
z.url();
z.emoji();         // 単一の絵文字を検証
z.base64();
z.base64url();
z.nanoid();
z.cuid();
z.cuid2();
z.ulid();
z.ipv4();
z.ipv6();
z.cidrv4();          // ip 範囲
z.cidrv6();          // ip 範囲
z.iso.date();
z.iso.time();
z.iso.datetime();
z.iso.duration();

メソッド形式(z.string().email())はまだ存在し、以前と同様に機能しますが、現在は非推奨です。

z.string().email(); // ❌ 非推奨
z.email(); // ✅ 

より厳格な .uuid()

z.uuid() は、RFC 9562/4122 仕様に対してより厳密に UUID を検証するようになりました。具体的には、仕様に従いバリアントビットが 10 である必要があります。より寛容な「UUID ライク」なバリデーターには、z.guid() を使用してください。

z.uuid(); // RFC 9562/4122 準拠の UUID
z.guid(); // 任意の 8-4-4-4-12 16進数パターン

.base64url() でパディングなし

z.base64url()(旧 z.string().base64url())では、パディングが許可されなくなりました。一般的に、base64url 文字列はパディングなしで URL セーフであることが望ましいです。

z.string().ip() の削除

これは、個別の .ipv4() および .ipv6() メソッドに置き換えられました。両方を受け入れる必要がある場合は、z.union() を使用して組み合わせてください。

z.string().ip() // ❌
z.ipv4() // ✅
z.ipv6() // ✅

z.string().ipv6() の更新

検証は、古い正規表現アプローチよりもはるかに堅牢な new URL() コンストラクタを使用して行われるようになりました。以前は検証に合格していた一部の無効な値が、現在は失敗する可能性があります。

z.string().cidr() の削除

同様に、これは個別の .cidrv4() および .cidrv6() メソッドに置き換えられました。両方を受け入れる必要がある場合は、z.union() を使用して組み合わせてください。

z.string().cidr() // ❌
z.cidrv4() // ✅
z.cidrv6() // ✅

z.coerce の更新

すべての z.coerce スキーマの入力タイプは unknown になりました。

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

.default() の更新

.default() の適用が微妙に変更されました。入力が undefined の場合、ZodDefault は解析プロセスをショートサーキットし、デフォルト値を返します。デフォルト値は 出力タイプ に代入可能でなければなりません。

const schema = z.string()
  .transform(val => val.length)
  .default(0); // 数値である必要があります
schema.parse(undefined); // => 0

Zod 3 では、.default()入力タイプ に一致する値を期待していました。ZodDefault はショートサーキットではなく、デフォルト値を解析していました。そのため、デフォルト値はスキーマの 入力タイプ に代入可能でなければなりませんでした。

// Zod 3
const schema = z.string()
  .transform(val => val.length)
  .default("tuna");
schema.parse(undefined); // => 4

古い動作を再現するために、Zod は新しい .prefault() API を実装しました。これは "pre-parse default"(解析前デフォルト)の略です。

// Zod 3
const schema = z.string()
  .transform(val => val.length)
  .prefault("tuna");
schema.parse(undefined); // => 4

z.object()

オプションフィールド内で適用されるデフォルト値

プロパティ内のデフォルト値は、オプションフィールド内であっても適用されます。これは期待とよりよく一致し、Zod 3 の長年のユーザビリティの問題を解決します。これは微妙な変更であり、キーの存在などに依存するコードパスが破損する可能性があります。

const schema = z.object({
  a: z.string().default("tuna").optional(),
});
 
schema.parse({});
// Zod 4: { a: "tuna" }
// Zod 3: {}

.strict().passthrough() の非推奨

これらのメソッドは一般的に不要になりました。代わりにトップレベルの z.strictObject()z.looseObject() 関数を使用してください。

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

これらのメソッドは後方互換性のためにまだ利用可能であり、削除されることはありません。これらはレガシーと見なされます。

.strip() の非推奨

これは z.object() のデフォルトの動作であったため、特に有用ではありませんでした。厳密なオブジェクトを「通常の」オブジェクトに変換するには、z.object(A.shape) を使用してください。

.nonstrict() の削除

長い間非推奨だった .strip() のエイリアスは削除されました。

.deepPartial() の削除

これは Zod 3 で長い間非推奨でしたが、Zod 4 で削除されました。この API の直接的な代替手段はありません。その実装には多くの落とし穴があり、その使用は一般的にアンチパターンです。

z.unknown() のオプション性の変更

z.unknown()z.any() タイプは、推論型で「キーがオプション」としてマークされなくなりました。

const mySchema = z.object({
  a: z.any(),
  b: z.unknown()
});
// Zod 3: { a?: any; b?: unknown };
// Zod 4: { a: any; b: unknown };

.merge() の非推奨

ZodObject.merge() メソッドは、.extend() を支持して非推奨となりました。.extend() メソッドは同じ機能を提供し、厳密性の継承に関する曖昧さを回避し、TypeScript のパフォーマンスも優れています。

// .merge (非推奨)
const ExtendedSchema = BaseSchema.merge(AdditionalSchema);
 
// .extend (推奨)
const ExtendedSchema = BaseSchema.extend(AdditionalSchema.shape);
 
// または分割代入を使用 (最高の tsc パフォーマンス)
const ExtendedSchema = z.object({
  ...BaseSchema.shape,
  ...AdditionalSchema.shape,
});

注意:さらに優れた TypeScript パフォーマンスを得るには、.extend() の代わりにオブジェクト分割代入を使用することを検討してください。詳細については API ドキュメント を参照してください。

z.nativeEnum() の非推奨

z.nativeEnum() 関数は z.enum() を支持して非推奨となりました。z.enum() API は、列挙型のような入力をサポートするようにオーバーロードされました。

enum Color {
  Red = "red",
  Green = "green",
  Blue = "blue",
}
 
const ColorSchema = z.enum(Color); // ✅

この ZodEnum のリファクタリングの一環として、長い間非推奨だった、重複する機能がいくつか削除されました。これらはすべて同一であり、歴史的な理由でのみ存在していました。

ColorSchema.enum.Red; // ✅ => "Red" (正規 API)
ColorSchema.Enum.Red; // ❌ 削除
ColorSchema.Values.Red; // ❌ 削除

z.array()

.nonempty() 型の変更

これは現在、z.array().min(1) と同一の動作をします。推論型は変更されません。

const NonEmpty = z.array(z.string()).nonempty();
 
type NonEmpty = z.infer<typeof NonEmpty>; 
// Zod 3: [string, ...string[]]
// Zod 4: string[]

古い動作は、z.tuple() と "rest" 引数を使用することでより良く表現されるようになりました。これは TypeScript の型システムにより近いです。

z.tuple([z.string()], z.string());
// => [string, ...string[]]

z.promise() の非推奨

z.promise() を使用する理由はほとんどありません。Promise である可能性のある入力がある場合は、Zod で解析する前に await してください。

z.function() と非同期関数を定義するために z.promise を使用している場合、それももはや必要ありません。以下の ZodFunction セクションを参照してください。

z.function()

z.function() の結果はもはや Zod スキーマではありません。代わりに、Zod で検証された関数を定義するためのスタンドアロンの「関数ファクトリ」として機能します。API も変更されました。args().returns() メソッドを使用する代わりに、inputoutput スキーマを事前に定義します。

const myFunction = z.function({
  input: [z.object({
    name: z.string(),
    age: z.number().int(),
  })],
  output: z.string(),
});
 
myFunction.implement((input) => {
  return `Hello ${input.name}, you are ${input.age} years old.`;
});

関数型を持つ Zod スキーマどうしても必要な場合は、この回避策 を検討してください。

.implementAsync() の追加

非同期関数を定義するには、implement() の代わりに implementAsync() を使用してください。

myFunction.implementAsync(async (input) => {
  return `Hello ${input.name}, you are ${input.age} years old.`;
});

.refine()

型述語の無視

Zod 3 では、型述語 (type predicate) をリファインメント関数として渡すと、スキーマの型を絞り込むことができました。これは文書化されていませんでしたが、いくつかの issue で議論されていました。これはもはや機能しません。

const mySchema = z.unknown().refine((val): val is string => {
  return typeof val === "string"
});
 
type MySchema = z.infer<typeof mySchema>; 
// Zod 3: `string`
// Zod 4: 依然として `unknown`

ctx.path の削除

Zod の新しい解析アーキテクチャでは、path 配列を積極的に評価しません。これは、Zod 4 の劇的なパフォーマンス向上を引き出すために必要な変更でした。

z.string().superRefine((val, ctx) => {
  ctx.path; // ❌ 利用不可
});

第2引数としての関数の削除

以下の恐ろしいオーバーロードは削除されました。

const longString = z.string().refine(
  (val) => val.length > 10,
  (val) => ({ message: `${val} is not more than 10 characters` })
);

z.ostring() などの削除

文書化されていない便利なメソッド z.ostring()z.onumber() などが削除されました。これらは、オプションの文字列スキーマを定義するための省略形でした。

z.literal()

symbol サポートの削除

Symbol はリテラル値とは見なされず、単純に === で比較することもできません。これは Zod 3 での見落としでした。

静的 .create() ファクトリの削除

以前はすべての Zod クラスが静的 .create() メソッドを定義していました。これらは現在、スタンドアロンのファクトリ関数として実装されています。

z.ZodString.create(); // ❌ 

z.record()

単一引数での使用の削除

以前は、z.record() は単一の引数で使用できました。これはサポートされなくなりました。

// Zod 3
z.record(z.string()); // ✅
 
// Zod 4
z.record(z.string()); // ❌
z.record(z.string(), z.string()); // ✅

列挙型サポートの向上

レコードはより賢くなりました。Zod 3 では、z.record() にキーのスキーマとして列挙型を渡すと、Partial 型になっていました。

const myRecord = z.record(z.enum(["a", "b", "c"]), z.number()); 
// { a?: number; b?: number; c?: number; }

Zod 4 では、これはもはや当てはまりません。推論型は期待通りになり、Zod は網羅性を保証します。つまり、解析中に入力にすべての列挙キーが存在することを確認します。

const myRecord = z.record(z.enum(["a", "b", "c"]), z.number());
// { a: number; b: number; c: number; }

オプションのキーを持つ古い動作を再現するには、z.partialRecord() を使用してください。

const myRecord = z.partialRecord(z.enum(["a", "b", "c"]), z.number());
// { a?: number; b?: number; c?: number; }

z.intersection()

マージ競合時に Error をスロー

Zod の交差型(intersection)は、2つのスキーマに対して入力を解析し、結果をマージしようとします。Zod 3 では、結果がマージできない場合、Zod は特別な "invalid_intersection_types" issue を持つ ZodError をスローしていました。

Zod 4 では、代わりに通常の Error がスローされます。マージできない結果の存在は、スキーマの構造的な問題、つまり2つの互換性のない型の交差を示しています。したがって、検証エラーよりも通常のエラーの方が適切です。

内部変更

一般的な Zod ユーザーは、この行より下のすべてを無視しても問題ありません。これらの変更は、ユーザー向けの z API には影響しません。

ここにリストするには内部変更が多すぎますが、特定の実装詳細に(意図的かどうかにかかわらず)依存している一般ユーザーに関連する可能性のあるものもあります。これらの変更は、Zod 上にツールを構築しているライブラリ作成者にとって特に興味深いでしょう。

ジェネリクスの更新

いくつかのクラスのジェネリック構造が変更されました。おそらく最も重要なのは、ZodType ベースクラスへの変更です:

// Zod 3
class ZodType<Output, Def extends z.ZodTypeDef, Input = Output> {
  // ...
}
 
// Zod 4
class ZodType<Output = unknown, Input = unknown> {
  // ...
}

2番目のジェネリック Def は完全に削除されました。代わりに、ベースクラスは OutputInput のみを追跡するようになりました。以前は Input 値はデフォルトで Output でしたが、現在はデフォルトで unknown です。これにより、z.ZodType を含むジェネリック関数が、多くの場合でより直感的に動作するようになります。

function inferSchema<T extends z.ZodType>(schema: T): T {
  return schema;
};
 
inferSchema(z.string()); // z.ZodString

z.ZodTypeAny の必要性はなくなりました。代わりに単に z.ZodType を使用してください。

z.core の追加

多くのユーティリティ関数と型が新しい zod/v4/core サブパッケージに移動され、Zod と Zod Mini 間でのコード共有が容易になりました。

import * as z from "zod/v4/core";
 
function handleError(iss: z.$ZodError) {
  // do stuff
}

利便性のため、zod/v4/core の内容は、zod および zod/mini から z.core 名前空間の下で再エクスポートされています。

import * as z from "zod";
 
function handleError(iss: z.core.$ZodError) {
  // do stuff
}

コアサブライブラリの内容の詳細については、Zod Core ドキュメントを参照してください。

._def の移動

._def プロパティは ._zod.def に移動されました。すべての内部 def の構造は変更される可能性があります。これはライブラリ作成者に関連しますが、ここでは包括的に文書化されません。

ZodEffects の削除

これはユーザー向けの API には影響しませんが、強調しておくべき内部変更です。これは、Zod が refinements を処理する方法のより大きな再構築の一部です。

以前は、refinements と transformations の両方が ZodEffects と呼ばれるラッパークラス内に存在していました。つまり、スキーマにどちらかを追加すると、元のスキーマが ZodEffects インスタンスでラップされていました。Zod 4 では、refinements はスキーマ自体の内部に存在します。より正確には、各スキーマは "checks" の配列を含んでいます。"check" の概念は Zod 4 で新しく導入されたもので、z.toLowerCase() のような副作用の可能性がある変換を含むように refinement の概念を一般化しています。

これは Zod Mini API で特に明らかで、.check() メソッドに大きく依存してさまざまな検証を組み合わせています。

import * as z from "zod/mini";
 
z.string().check(
  z.minLength(10),
  z.maxLength(100),
  z.toLowerCase(),
  z.trim(),
);

ZodTransform の追加

一方、transforms は専用の ZodTransform クラスに移動されました。このスキーマクラスは入力変換を表します。実際、現在はスタンドアロンの transformations を定義することができます:

import * as z from "zod";
 
const schema = z.transform(input => String(input));
 
schema.parse(12); // => "12"

これは主に ZodPipe と組み合わせて使用されます。.transform() メソッドは現在、ZodPipe のインスタンスを返します。

z.string().transform(val => val); // ZodPipe<ZodString, ZodTransform>

ZodPreprocess の削除

.transform() と同様に、z.preprocess() 関数も専用の ZodPreprocess インスタンスではなく、ZodPipe インスタンスを返すようになりました。

z.preprocess(val => val, z.string()); // ZodPipe<ZodTransform, ZodString>

ZodBranded の削除

ブランディング(Branding)は、専用の ZodBranded クラスではなく、推論型への直接変更で処理されるようになりました。ユーザー向けの API は同じままです。

On this page

エラーのカスタマイズ (Error customization)
message の非推奨
invalid_type_errorrequired_error の削除
errorMap の削除
ZodError
issue フォーマットの更新
エラーマップ優先度の変更
.format() の非推奨
.flatten() の非推奨
.formErrors の削除
.addIssue().addIssues() の非推奨
z.number()
無限値なし
.safe() は浮動小数点数(floats)を受け付けなくなりました
.int() は安全な整数のみを受け入れます
z.string() の更新
.email() などの非推奨
より厳格な .uuid()
.base64url() でパディングなし
z.string().ip() の削除
z.string().ipv6() の更新
z.string().cidr() の削除
z.coerce の更新
.default() の更新
z.object()
オプションフィールド内で適用されるデフォルト値
.strict().passthrough() の非推奨
.strip() の非推奨
.nonstrict() の削除
.deepPartial() の削除
z.unknown() のオプション性の変更
.merge() の非推奨
z.nativeEnum() の非推奨
z.array()
.nonempty() 型の変更
z.promise() の非推奨
z.function()
.implementAsync() の追加
.refine()
型述語の無視
ctx.path の削除
第2引数としての関数の削除
z.ostring() などの削除
z.literal()
symbol サポートの削除
静的 .create() ファクトリの削除
z.record()
単一引数での使用の削除
列挙型サポートの向上
z.intersection()
マージ競合時に Error をスロー
内部変更
ジェネリクスの更新
z.core の追加
._def の移動
ZodEffects の削除
ZodTransform の追加
ZodPreprocess の削除
ZodBranded の削除