ZodErrorの中身とエラーメッセージの日本語化
この章の目次開く
safeParse が失敗したとき、result.error に入っているのが ZodError です。既定のメッセージは英語で、開発者向けの文言なので、そのままユーザーに見せることはできません。この章でその整形方法を扱います。
issues — 失敗の一覧
ZodError の中身は issues という配列です。失敗した項目の数だけ要素が入ります。
const schema = z.object({ name: z.string().min(1), age: z.number() });
const result = schema.safeParse({ name: '', age: 'x' });
result.error.issues;
// [
// { code: 'too_small', path: ['name'],
// message: 'Too small: expected string to have >=1 characters' },
// { code: 'invalid_type', path: ['age'],
// message: 'Invalid input: expected number, received string' }
// ]主なプロパティです。
| プロパティ | 内容 |
|---|---|
code | 失敗の種類(invalid_type / too_small / custom など) |
path | どの項目か。ネストは ['user', 'name']、配列は ['tags', 1] |
message | 人が読むメッセージ |
path があるので「どの入力欄のエラーか」を機械的に振り分けられます。
整形用のヘルパー
issues の配列をそのまま画面に流すのは扱いづらいので、用途別の変換関数が用意されています。
| 関数 | 返る形 | 向いている用途 |
|---|---|---|
z.treeifyError(error) | 構造をそのまま辿れる入れ子オブジェクト | ネストしたフォーム |
z.flattenError(error) | fieldErrors / formErrors の平坦な形 | 1階層のフォーム |
z.prettifyError(error) | 人が読む複数行の文字列 | ログ・開発時の確認 |
z.flattenError(result.error);
// {
// formErrors: [],
// fieldErrors: {
// name: ['Too small: expected string to have >=1 characters'],
// age: ['Invalid input: expected number, received string']
// }
// }z.treeifyError(result.error);
// { errors: [], properties: {
// name: { errors: ['Too small: ...'] },
// age: { errors: ['Invalid input: ...'] } } }console.log(z.prettifyError(result.error));
// ✖ Too small: expected string to have >=1 characters
// → at name
// ✖ Invalid input: expected number, received string
// → at agefieldErrors は「項目名 → メッセージ配列」なので、フォームの各入力欄の下に出すのがそのまま書けます。ネストの無いフォームなら flattenError が一番扱いやすいです。

メッセージを日本語にする3つの方法
1. ロケールを適用してまとめて日本語にする
Zodには日本語ロケールが同梱されています。アプリの起動時に1回設定すれば、既定のメッセージがすべて日本語になります。
import { z } from 'zod';
z.config(z.locales.ja());適用の前後で、同じ検証のメッセージがこう変わります。
| 検証 | 適用前 | 適用後 |
|---|---|---|
z.string() に数値 | Invalid input: expected string, received number | 無効な入力: stringが期待されましたが、数値が入力されました |
z.string().min(8) | Too small: expected string to have >=8 characters | 小さすぎる値: stringは8文字以上である必要があります |
z.email() | Invalid email address | 無効なメールアドレス |
2. メソッドの第2引数で指定する
個別の検証にメッセージを添えます。もっとも手軽で、実務でよく使う形です。
z.string().min(8, 'パスワードは8文字以上で入力してください');
z.array(z.string()).min(1, 'タグを1つ以上選んでください');3. スキーマ生成時に error で指定する
型そのものが違う場合(文字列を期待したのに数値が来た等)のメッセージは、スキーマの引数で指定します。
z.string({ error: '文字列で入力してください' });実務での組み立て方
APIのハンドラでは、safeParse の失敗をそのまま400として返す形が定番です。
const result = createUserSchema.safeParse(body);
if (!result.success) {
return Response.json(
{ message: '入力内容を確認してください', errors: z.flattenError(result.error).fieldErrors },
{ status: 400 },
);
}全項目分のエラーがまとめて返るので、フロントは1回のレスポンスですべての入力欄にエラーを表示できます。1件ずつ指摘して何度も送信させる、という体験を避けられます。
学習者エラーの中身をそのままレスポンスに載せて大丈夫なの?
先生fieldErrors くらいなら問題ないよ。ただし error オブジェクト全体をJSONにするのは避けよう。内部のスキーマ構造が推測できる情報が混ざることがあるからね。返すのは「項目名」と「メッセージ」だけに絞るのが安全だよ。
よくあるハマりどころ
英語のメッセージがそのまま画面に出る
既定のメッセージは英語です。ロケール適用か、項目ごとのメッセージ指定が必要です。
required_error を書いても変わらない
v3の書き方です。v4では error を使います。
refine のメッセージが Invalid input になる
refineの章のとおり、refine は第2引数を省略すると Invalid input になります。
エラーが画面に出ない
path が空だと、どの入力欄にも紐づきません。オブジェクト全体に掛けた refine では path を指定してください。
ちゃんと使うためのポイント
ZodErrorの本体はissuesの配列。code/path/messageを持つ-
1階層のフォームなら
z.flattenError()のfieldErrorsが一番扱いやすい z.config(z.locales.ja())で既定メッセージを日本語化できるが、語彙は開発者向けのまま- ユーザーに見せる文言は、メソッドの第2引数か
{ error: '...' }で個別に書く - v3の
required_errorはv4では無視される
次の章では、フォームやURLクエリのようにすべてが文字列で届くデータを扱う z.coerceと環境変数の検証 に進みます。ここには「空欄が0になる」という有名な落とし穴があります。
参考リンク
- Error handling - Zod — エラーのカスタマイズ方法の公式解説(英語)