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

Customizing errors

In Zod, validation errors are surfaced as instances of the z.core.$ZodError class.

The zod/v4 package uses a subclass of this called z.ZodError that implements some additional convenience methods.

Instances of $ZodError contain an .issues array. Each issue contains a human-readable message and additional structured metadata about the issue.

import { z } from "zod/v4";
 
const result = z.string().safeParse(12); // { success: false, error: ZodError }
result.error.issues;
// [
//   {
//     expected: 'string',
//     code: 'invalid_type',
//     path: [],
//     message: 'Invalid input: expected string, received number'
//   }
// ]

Every issue contains a message property with a human-readable error message. Error messages can be customized in a number of ways.

The error param

Virtually every Zod API accepts an optional error message parameter.

z.string("Not a string!");

This custom error will show up as the message property of any validation issues that originate from this schema.

z.string("Not a string!").parse(12);
// ❌ throws ZodError {
//   issues: [
//     {
//       expected: 'string',
//       code: 'invalid_type',
//       path: [],
//       message: 'Not a string!'   <-- 👀 custom error message
//     }
//   ]
// }

All z functions and schema methods accept custom errors.

z.string("Bad!");
z.string().min(5, "Too short!");
z.uuid("Bad UUID!");
z.iso.date("Bad date!");
z.array(z.string(), "Bad array!");
z.array(z.string()).min(5, "Too few items!");
z.set(z.string(), "Bad set!");

If you prefer, you can pass a params object with an error parameter instead.

z.string({ error: "Bad!" });
z.string().min(5, { error: "Too short!" });
z.uuid({ error: "Bad UUID!" });
z.iso.date({ error: "Bad date!" });
z.array(z.string(), { error: "Bad array!" });
z.array(z.string()).min(5, { error: "Too few items!" });
z.set(z.string(), { error: "Bad set!" });

The error param optionally accepts a function. This function will be called at parse time if a valiation error occurs.

z.string({ error: ()=>`[${Date.now()}]: Validation failure.` });

Note — In Zod v3, there were separate params for message (a string) and errorMap (a function). These have been unified in Zod 4 as error.

The error function received a context object you can use to customize the error message based on the input or other validation information.

z.string({
  error: (iss) => iss.input===undefined ? "Field is required." : "Invalid input."
});

For advanced cases, the iss object provides additional information you can use to customize the error.

z.string({
  error: (iss) => {
    iss.code; // the issue code
    iss.input; // the input data
    iss.inst; // the schema/check that originated this issue
    iss.path; // the path of the error
  },
});

Depending on the API you are using, there may be additional properties available. Use TypeScript's autocomplete to explore the available properties.

z.string().min(5, {
  error: (iss) => {
    // ...the same as above
    iss.minimum; // the minimum value
    iss.inclusive; // whether the minimum is inclusive
    return `Password must have ${iss.minimum} characters or more`;
  },
});

Per-parse error customization

To customize errors on a per-parse basis, pass an error map into the parse method:

const schema = z.string()
 
schema.parse(12, {
  error: iss => "per-parse custom error"
};

This has lower precedence than any schema-level custom messages.

const schema = z.string({ error: "highest priority" });
const result = schema.safeParse(12, {
  error: (iss) => "lower priority",
})
 
result.error.issues;
// [{ message: "highest priority", ... }]

The iss object is a discriminated union of all possible issue types. Use the code property to discriminate between them.

For a breakdown of all Zod issue codes, see the zod/v4/core documentation.

const result = schema.safeParse(12, {
  error: (iss) => {
    if (iss.code === "invalid_type") {
      return `invalid type, expected ${iss.expected}`;
    }
    if (iss.code === "too_small") {
      return `minimum is ${iss.minimum}`;
    }
    // ...
  }
})

Global error customization

To specify a global error map, use z.config() to set Zod's customError configuration setting:

z.config({
  customError: (iss) => {
    return "globally modified error";
  },
});

Global error messages have lower precedence than schema-level or per-parse error messages.

The iss object is a discriminated union of all possible issue types. Use the code property to discriminate between them.

For a breakdown of all Zod issue codes, see the zod/v4/core documentation.

const result = schema.safeParse(12, {
  error: (iss) => {
    if (iss.code === "invalid_type") {
      return `invalid type, expected ${iss.expected}`;
    }
    if (iss.code === "too_small") {
      return `minimum is ${iss.minimum}`;
    }
    // ...
  }
})

Internationalization

To support internationalization of error message, Zod provides several built-in locales. These are exported from the zod/v4/core package.

Note — The zod/v4 library automatically loads the en locale automatically. The zod/v4-mini sub-package does not load any locale; instead all error messages default to Invalid input.

import { z } from "zod/v4";
import en from "zod/v4/locales/en"
 
z.config(en());

To lazily load a locale, consider dynamic imports:

import { z } from "zod/v4";
 
async function loadLocale(locale: string) {
  const { default: locale } = await import(`zod/v4/locales/${locale}`);
  z.config(locale());
};
 
await loadLocale("fr");

For convenience, all locales are exported as z.locales from zod/v4/locales.

import { z } from "zod/v4";
 
z.config(z.locales.en());

Locales

The following locales are available:

  • ar — Arabic
  • az — Azerbaijani
  • be — Belarusian
  • ca — Catalan
  • cs — Czech
  • de — German
  • en — English
  • es — Spanish
  • fa — Farsi
  • fi — Finnish
  • fr — French
  • frCA — Canadian French
  • he — Hebrew
  • hu — Hungarian
  • id — Indonesian
  • it — Italian
  • ja — Japanese
  • ko — Korean
  • mk — Macedonian
  • ms — Malay
  • no — Norwegian
  • ota — Türkî
  • pl — Polish
  • pt — Portuguese
  • ru — Russian
  • sl — Slovenian
  • ta — Tamil
  • th — Thai
  • tr — Türkçe
  • ua — Ukrainian
  • ur — Urdu
  • vi — Tiếng Việt
  • zhCN — Simplified Chinese
  • zhTW — Traditional Chinese

Error precedence

Below is a quick reference for determining error precedence: if multiple error customizations have been defined, which one takes priority? From highest to lowest priority:

  1. Schema-level error — Any error message "hard coded" into a schema definition.
z.string("Not a string!");
  1. Per-parse error — A custom error map passed into the .parse() method.
z.string().parse(12, {
  error: (iss) => "My custom error"
});
  1. Global error map — A custom error map passed into z.config().
z.config({
  customError: (iss) => "My custom error"
});
  1. Locale error map — A custom error map passed into z.config().
z.config(z.locales.en());

On this page