Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/colinhacks/zod/llms.txt

Use this file to discover all available pages before exploring further.

Error Maps

Error maps allow you to customize error messages based on the validation issue type and context.

Basic Error Map

An error map is a function that receives an issue and returns a custom message:
import * as z from 'zod';

const errorMap: z.ZodErrorMap = (issue) => {
  if (issue.code === "invalid_type") {
    if (issue.expected === "string") {
      return { message: "bad type!" };
    }
  }
  if (issue.code === "custom") {
    return { message: `less-than-${issue.params?.minimum}` };
  }
  return undefined; // Use default message
};

const result = z.string().safeParse(234, { error: errorMap });
// Error message: "bad type!"

Error Map Return Values

Error maps can return:
  • { message: string } - Custom error message
  • string - Shorthand for { message: string }
  • undefined or null - Fall back to default message
const errorMap: z.ZodErrorMap = (issue) => {
  // Object form
  if (issue.code === "invalid_type") {
    return { message: "Invalid type" };
  }
  
  // String shorthand
  if (issue.code === "too_small") {
    return "Too small";
  }
  
  // Fall back to default
  return undefined;
};

Contextual Error Maps

Apply error maps to specific parse operations:
const schema = z.string();

// Use custom error map for this parse only
const result = schema.safeParse(123, {
  error: (issue) => ({ message: "contextual" })
});

Refinements with Error Maps

const errorMap: z.ZodErrorMap = (issue) => {
  if (issue.code === "custom") {
    return { message: `less-than-${issue.params?.minimum}` };
  }
  return undefined;
};

const schema = z.number().refine((val) => val >= 3, {
  params: { minimum: 3 }
});

const result = schema.safeParse(2, { error: errorMap });
// {
//   "code": "custom",
//   "path": [],
//   "params": { "minimum": 3 },
//   "message": "less-than-3"
// }

Schema-Bound Error Maps

Bind error maps directly to schemas:
const stringWithCustomError = z.string({
  error: () => "bound"
});

const result = stringWithCustomError.safeParse(1234);
// message: "bound"

Bound vs Contextual Precedence

Schema-bound error maps take precedence over contextual ones:
const boundSchema = z.string({
  error: () => "bound"
});

// Contextual error map is ignored
const result = boundSchema.safeParse(undefined, {
  error: () => ({ message: "contextual" })
});
// message: "bound" (not "contextual")

Global Error Configuration

Set a global custom error map using z.config():
// Set global error map
z.config({
  customError: () => ({ message: "override" })
});

const schema = z.string().min(10);
const result = schema.safeParse("tooshort");
// All errors use the global error map

// Clear global error map
z.config({ customError: undefined });
Global error maps affect all validations in your application. Use with caution and consider schema-bound or contextual error maps for more targeted customization.

Error Map Issue Types

Invalid Type Issues

const errorMap: z.ZodErrorMap = (issue) => {
  if (issue.code === "invalid_type") {
    const expected = issue.expected; // "string" | "number" | "boolean" | ...
    const received = issue.input; // The actual input value
    return { message: `Expected ${expected}, got ${typeof received}` };
  }
  return undefined;
};

Size Constraint Issues

const errorMap: z.ZodErrorMap = (issue) => {
  if (issue.code === "too_small") {
    const { origin, minimum, inclusive, exact } = issue;
    if (origin === "string") {
      return { message: `String must be at least ${minimum} characters` };
    }
    if (origin === "array") {
      return { message: `Array must contain at least ${minimum} items` };
    }
  }
  
  if (issue.code === "too_big") {
    const { origin, maximum } = issue;
    return { message: `${origin} exceeds maximum of ${maximum}` };
  }
  
  return undefined;
};

Invalid Format Issues

const errorMap: z.ZodErrorMap = (issue) => {
  if (issue.code === "invalid_format") {
    const format = issue.format; // "email" | "url" | "uuid" | "regex" | ...
    
    if (format === "email") {
      return { message: "Please enter a valid email address" };
    }
    
    if (format === "regex" && issue.pattern) {
      return { message: `Must match pattern: ${issue.pattern}` };
    }
  }
  return undefined;
};

Custom Issues with Params

const errorMap: z.ZodErrorMap = (issue) => {
  if (issue.code === "custom" && issue.params) {
    // Access custom params from refinements
    if ('minimum' in issue.params) {
      return { message: `Value must be at least ${issue.params.minimum}` };
    }
    if ('field' in issue.params) {
      return { message: `Invalid ${issue.params.field}` };
    }
  }
  return undefined;
};

Advanced Error Map Patterns

Path-Aware Error Messages

const errorMap: z.ZodErrorMap = (issue) => {
  const path = issue.path ?? [];
  
  if (path.length > 0) {
    const fieldName = path[path.length - 1];
    return { message: `Invalid ${fieldName}: ${issue.code}` };
  }
  
  return undefined;
};

const schema = z.object({
  items: z.array(z.string()).refine(
    (data) => data.length > 3,
    { path: ["items-too-few"] }
  )
});

const result = schema.safeParse({ items: ["first"] }, { error: errorMap });
// Path will be ["items", "items-too-few"]

Unrecognized Keys

const errorMap: z.ZodErrorMap = (issue) => {
  if (issue.code === "unrecognized_keys") {
    const keys = issue.keys; // Array of unrecognized key names
    return { message: `Unknown fields: ${keys.join(", ")}` };
  }
  return undefined;
};

Invalid Union

const errorMap: z.ZodErrorMap = (issue) => {
  if (issue.code === "invalid_union") {
    if (issue.errors.length > 0) {
      // No match found
      return { message: "Value doesn't match any of the expected types" };
    } else if (issue.inclusive === false) {
      // Multiple matches (for non-inclusive unions)
      return { message: "Value matches multiple types" };
    }
  }
  return undefined;
};

Combining Error Messages

Hard-Coded Message with Error Map

const schema = z.string().refine(
  (val) => val.length > 12,
  {
    params: { minimum: 13 },
    message: "override" // This takes precedence
  }
);

const result = schema.safeParse("asdf", {
  error: () => "contextual"
});
// message: "override" (hard-coded message wins)

Error and Message Conflict

// This will throw an error - cannot use both
try {
  z.string().refine((_) => true, {
    message: "override",
    error: (iss) => iss.input === undefined ? "asdf" : null
  });
} catch (e) {
  // Error: Cannot use both 'message' and 'error' options
}
You cannot use both message and error options together. The message option is a shorthand that takes precedence.

Empty String Messages

You can explicitly set empty error messages:
const schema = z.string().max(1, { message: "" });
const result = schema.safeParse("asdf");
// message: ""

Best Practices

  1. Return undefined for defaults - Let Zod generate standard messages when appropriate
  2. Use params for context - Pass metadata through params for dynamic messages
  3. Prefer schema-bound for reusable schemas - Bind error maps to schemas you’ll reuse
  4. Use contextual for one-off customization - Apply custom error maps at parse time for specific cases
  5. Avoid global error maps in libraries - They affect all Zod usage in the application
  6. Consider internationalization - Error maps are perfect for translating messages

Build docs developers (and LLMs) love