Skip to Content

Last Updated: 3/12/2026


Error Handling

When validation fails, Zod provides detailed error information to help you understand what went wrong and communicate issues to users.

Throwing Errors with .parse()

The .parse() method throws a ZodError when validation fails:

import * as z from "zod"; const User = z.object({ username: z.string(), age: z.number(), }); try { User.parse({ username: 123, age: "25" }); } catch (err) { if (err instanceof z.ZodError) { console.log(err.issues); /* [ { code: 'invalid_type', expected: 'string', received: 'number', path: ['username'], message: 'Expected string, received number' }, { code: 'invalid_type', expected: 'number', received: 'string', path: ['age'], message: 'Expected number, received string' } ] */ } }

Safe Parsing with .safeParse()

To avoid try/catch blocks, use .safeParse(). It returns a result object with a success property:

const result = User.safeParse({ username: 123, age: "25" }); if (!result.success) { // Validation failed console.log(result.error.issues); } else { // Validation succeeded console.log(result.data); }

The result is a discriminated union , so TypeScript automatically narrows the type based on success.

Error Structure

Each issue in ZodError.issues contains:

  • code — The error type (e.g., invalid_type, too_small, invalid_string)
  • path — The path to the invalid field (e.g., ['user', 'email'])
  • message — A human-readable error message
  • Additional properties — Vary by error code (e.g., expected, received, minimum, maximum)

Formatting Errors

Zod provides helpers to format errors for display:

const result = User.safeParse({ username: 123, age: "25" }); if (!result.success) { // Flatten errors into a simple object const formatted = result.error.flatten(); console.log(formatted.fieldErrors); /* { username: ['Expected string, received number'], age: ['Expected number, received string'] } */ }

Async Validation

If your schema uses async refinements or transforms, use .parseAsync() or .safeParseAsync():

const schema = z.string().refine(async (val) => { // Async validation logic return val.length <= 8; }); await schema.parseAsync("hello"); // ✓ await schema.parseAsync("verylongstring"); // ✗ throws ZodError const result = await schema.safeParseAsync("hello"); // => { success: true, data: "hello" }

What’s Next