Looking to prevent unexpected undefined values and make your Zod schemas more predictable? Zod’s default() method is your secret weapon.
The default() ensures your fields always have a safe fallback — without forcing your users to provide every value.
This guide walks you through how defaults actually work under the hood, shows real usage patterns, and explains common mistakes developers make when mixing defaults with transforms, optional values, and complex types.
Note: We used Zod v4 to run and test all the default() code examples in this guide, ensuring everything works exactly as shown.
How Zod Handles Defaults on null and Validation Errors
Zod’s default() method only triggers when the input value is undefined. This is the key rule that decides whether your default runs or gets ignored.
Because of this, developers often expect defaults to apply when a value is null or when validation fails, but Zod doesn’t behave that way out of the box.
Default on null
If a field receives null, Zod does not treat it as “missing.”
null goes through validation like any other value, and if the schema doesn’t allow null, the result is a validation error — not a fallback to the default.
z.string().default("hello")
To allow null but still use a default, you must explicitly convert it to undefined or transform it:
z.string().nullable().transform(val => val ?? undefined).default("hello");
Default on Validation Error
Zod does not auto-fallback to your default when a value is invalid.
If the value is wrong — wrong type, wrong format, wrong shape — Zod will throw an error even when a default exists. So use prefault() method.
Zod default vs prefault
- default()
- Runs only when the input is
undefined. - Does not run for
nullor invalid values. - Does not override validation errors.
- Applied at the end of parsing after checking if value is missing.
z.string().default("hi").parse(undefined)
- Runs only when the input is
- prefault()
- Runs before validation.
- Converts any invalid input into the fallback before schema rules apply.
- Useful for pre-cleaning bad input or replacing null, empty strings, or bad types.
z.string().prefault("hi").parse(null);
Zod default vs catch
- default()
- Only triggers when input is
undefined. - Cannot recover from validation failures.
- Only triggers when input is
- catch()
- Triggers only when validation fails.
- Great for fallback-on-error behavior.
z.number().catch(10).parse("oops")
Zod default vs optional
- default()
- Converts
undefined→ fallback value. - Field becomes implicitly optional if a default is provided.
- Still validates when a user provides a value.
- Converts
- optional()
- Allows
undefinedas a valid input. - Does not apply any fallback.
- The output may still be
undefined. z.string().optional().parse(undefined)
- Allows
Using Zod Defaults Across Different Data Types
Here’s how each Zod datatype behaves with default() and how you’d actually use them in real projects.
String Default
Setting a default username when a user signs up without providing one.
const UsernameSchema = z.string().default("guest");
Number Default
Auto-set the page number in pagination APIs. Parse integer 1 as default.
const PageSchema = z.number().int().default(1);
Boolean Default
Enable/disable email notifications by default in a profile form. Can default to true or false.
const EmailNotifySchema = z.boolean().default(true);
Array Default
Ensure an API always receives an array of tags, even if the client sends nothing. Default to empty array.
const TagsSchema = z.array(z.string()).default([]);
Object Default
Fallback user preferences when the client doesn’t send them. Default to empty object.
const PrefsSchema = z.object({
theme: z.string().default("light"),
fontSize: z.number().default(14),
}).default({});
Enum Default
Assign a default user role during registration.
const RoleSchema = z.enum(["user", "admin"]).default("user");
Literal Default
Versioning API requests where a default version should always exist.
const VersionSchema = z.literal("v1").default("v1");
Date Default
Assign the current timestamp when log entries are created.
const LogDateSchema = z.date().default(() => new Date());
Conditional Default Value
Sometimes you need a default value that depends on other fields or depends on the incoming value itself. Zod doesn’t have a built-in “conditional default” method, but you can achieve it using transform, superRefine, or default + transform combos.
Example: If a string is empty, assign a fallback.
z.string()
.transform(val => val.trim() === "" ? "guest" : val)
.default("guest");
Dynamic Default Value
A dynamic default means the value is calculated at parse time, not hardcoded.
Zod achieves this by passing a function into .default(() => … ).
This is perfect when your default depends on:
- the current date
- random IDs
- an environment value
Example: Dynamic default ID (random / UUID)
const UserSchema = z.object({
id: z.string().default(() => randomUUID()),
username: z.string(),
});
Summary
Zod’s default() gives your schemas safe fallback values, but it only runs when the input is undefined. It does not apply defaults for null or invalid values — those will still throw validation errors unless you explicitly handle them. That’s where prefault() (fallback before validation) and catch() (fallback on validation error) come in.
Key takeaways:
- default() → triggers only on
undefined; doesn’t fix invalid input. - prefault() → replaces bad input before validation; great for cleaning nulls, empty strings, or incorrect types.
- catch() → applies when validation fails; perfect for error-based fallbacks.
- optional() → allows undefined but doesn’t supply a fallback.
Defaults behave consistently across all data types — strings, numbers, booleans, arrays, objects, enums, literals, and dates — and you can use transforms to emulate conditional or dynamic defaults when needed. Passing a function into .default() lets you generate values at parse time (e.g., timestamps, UUIDs).
In short: Zod defaults make schemas more predictable, but understanding when they trigger (and when they don’t) is essential for avoiding unexpected undefined values and building safer data workflows.