Capítulo 44 de 61

Chapter 44: Formatting Errors

Core Idea

Zod provides three converters for turning a flat ZodError.issues array into a more consumable shape: z.treeifyError() (nested tree mirroring the schema), z.prettifyError() (human-readable string), and z.flattenError() (shallow object, best for one-level-deep schemas); z.formatError() is the deprecated predecessor of treeifyError.

Key Concepts

  • z.treeifyError(error): builds a nested object mirroring the schema's shape. Each level has an errors array (messages at that exact path) plus properties (for object keys) or items (for array indices) to descend further. Always access nested levels with optional chaining, since intermediate levels may be absent.
  • z.prettifyError(error): returns a single formatted string with one line per issue and a → at path line for non-root issues — good for CLI output or quick debugging, not for programmatic branching.
  • z.formatError(error): deprecated, replaced by treeifyError; same nested-tree idea but uses _errors as the key instead of errors.
  • z.flattenError(error): for schemas that are just one level deep, returns { formErrors: string[], fieldErrors: { [key]: string[] } }formErrors holds root-level issues (path: []), fieldErrors holds per-field issues keyed by field name. Simpler to consume than treeifyError when there's no real nesting to preserve.

Code Examples

const schema = z.strictObject({
  username: z.string(),
  favoriteNumbers: z.array(z.number()),
});

const result = schema.safeParse({
  username: 1234,
  favoriteNumbers: [1234, "4567"],
  extraKey: 1234,
});

const tree = z.treeifyError(result.error);
tree.properties?.username?.errors;
// => ["Invalid input: expected string, received number"]
tree.properties?.favoriteNumbers?.items?.[1]?.errors;
// => ["Invalid input: expected number, received string"]

z.prettifyError(result.error);
// "✖ Unrecognized key: \"extraKey\"\n✖ Invalid input: expected string, received number\n  → at username\n..."

const flattened = z.flattenError(result.error);
flattened.fieldErrors.username;         // => ["Invalid input: expected string, received number"]
flattened.formErrors;                   // => ['Unrecognized key: "extraKey"']
  • What it demonstrates: the same three-issue error rendered through all three formatters, showing how each structures the same information differently.

Anti-patterns

  • Using z.formatError() in new code: deprecated — use z.treeifyError().
  • Reaching for z.treeifyError() on a flat, one-level schema: z.flattenError()'s fieldErrors/formErrors shape is simpler to consume when there's no real nesting to represent.

Key Takeaways

  1. Pick the formatter by consumer: prettifyError for human/CLI output, treeifyError for programmatically walking a nested form, flattenError for simple field-by-field form validation UIs.
  2. All three formatters preserve every issue's message and path — they differ only in shape, not in information content.
  3. Optional chaining is required when traversing treeifyError's/formatError's output, since not every path in the schema necessarily has a corresponding error node.

Connects To

  • Basic Usage — Define, Parse, Handle Errors, Infer: ZodError.issues, the raw data these formatters all consume.
  • The error Param & Schema-Level Customization: for changing the messages themselves, as opposed to reshaping the error structure after the fact.