How to Use Zod's Catch Method for Default Values on Parse Failures

Zod's catch method creates a $ZodCatch wrapper schema that returns a static or computed fallback value whenever inner validation fails, ensuring your application receives valid data instead of throwing errors.

Zod's catch method provides a defensive mechanism for handling validation failures by supplying default values at the schema level. As implemented in the colinhacks/zod repository, this feature intercepts parsing errors in packages/zod/src/v4/core/schemas.ts and substitutes your specified fallback before the error propagates. This approach eliminates the need for try/catch blocks around every parse call while maintaining type safety.

How Zod's Catch Method Works Internally

The catch method generates a wrapper schema that executes a six-step resolution process during parsing. According to the source code in packages/zod/src/v4/core/schemas.ts, the runtime behavior follows this exact sequence:

  1. Schema Instantiation: Calling z.catch(inner, catchValue) in packages/zod/src/v4/core/api.ts (lines 1495-1507) creates a $ZodCatch instance that stores the inner schema and your fallback value or function.

  2. Inner Validation Execution: The wrapper first runs innerType._zod.run to validate the input against the original schema requirements (lines 3764-3769).

  3. Failure Detection: If the inner result contains issues (verified via result.issues.length at lines 3784-3790), Zod identifies the validation as failed.

  4. Fallback Computation: The stored catchValue receives a context object containing ctx.error.issues, the original payload, and raw input, then returns the replacement value (lines 3786-3792).

  5. Issue Suppression: After successful fallback execution, Zod empties the issues array (payload.issues = []) to ensure the outer parse call succeeds (lines 3793-3795).

  6. Result Return: The modified payload containing your default value returns to the caller, effectively masking the original validation failure.

The tree-shakable mini build in packages/zod/src/v4/mini/schemas.ts (lines 1541-1552) implements this identical logic for bundle-conscious applications.

Implementing Default Values with Zod Catch

Static Fallback Values

Provide a constant default that applies regardless of why validation failed. This pattern works best for configuration objects or primitive types with clear safe defaults.

import { z } from "zod";

const schema = z.catch(z.string(), "fallback");

console.log(z.parse(schema, "hello")); // → "hello"
console.log(z.parse(schema, 123));     // → "fallback"

Dynamic Defaults Based on Validation Errors

Access the error context to compute conditional defaults using the ctx parameter, which exposes the specific validation issues that occurred.

import { z } from "zod";

const schema = z.catch(z.string(), (ctx) => {
  // ctx.error.issues contains the array of validation failures
  return `${ctx.error.issues.length} validation error(s) detected`;
});

console.log(z.parse(schema, 42)); // → "1 validation error(s) detected"

SafeParse Integration

Even when using z.safeParse, the catch wrapper guarantees success: true by resolving failures before the result object is constructed.

import { z } from "zod";

const schema = z.catch(z.object({ age: z.number() }), { age: 0 });

const result = z.safeParse(schema, { name: "Alice" });
console.log(result.success); // true
console.log(result.data);    // { age: 0 }

Tree-Shakable Mini Build

The lightweight zod/mini export supports identical catch functionality for applications requiring minimal bundle size.

import { z } from "zod/mini";

const schema = z.catch(z.number(), () => 99);
console.log(z.parse(schema, "not a number")); // → 99

Key Source Files in the Zod Repository

Understanding the implementation requires familiarity with these specific files:

Summary

  • Zod's catch method creates a $ZodCatch wrapper that intercepts validation failures in packages/zod/src/v4/core/schemas.ts before they throw.
  • Static fallbacks provide constant default values, while dynamic fallbacks receive a context object with ctx.error.issues for conditional logic based on specific failure types.
  • The implementation clears the internal issues array after fallback execution (payload.issues = []), ensuring parse returns successfully rather than propagating errors.
  • Both the standard and mini builds (zod/mini) support identical catch functionality with the same six-step resolution process.

Frequently Asked Questions

What is the difference between Zod's catch and default methods?

z.default() provides a fallback only when the input is explicitly undefined, whereas z.catch() activates when any validation error occurs—including type mismatches, constraint violations, or malformed data. Use catch when you need to handle invalid inputs gracefully, and default when you only need to handle missing values.

Can I access the original input value in the catch function?

Yes, the catch function receives a context object that includes the original payload, raw input, and a complete error object with the issues array. This allows you to inspect the failed value or error details when computing your fallback, as shown in the dynamic defaults example.

Does Zod catch work with asynchronous schemas?

The catch method supports both synchronous and asynchronous parsing workflows. If the inner schema uses parseAsync, the $ZodCatch wrapper properly handles the Promise rejection and applies your fallback value or function to the resolved error.

How does catch affect TypeScript type inference?

The catch method preserves the inner schema's output type while widening the accepted input to unknown, ensuring TypeScript recognizes that the schema will always return the expected output type regardless of what input is provided. This maintains strict type safety downstream while allowing flexible input handling.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →