Zod Dynamic Default: Functions as Default Values
A Zod dynamic default lets you assign a default value using a function, so the fallback is generated at parse time. This is essential when defaults need to be unique, time-sensitive, or environment-aware—such as generating timestamps, UUIDs, or random tokens.
import { z } from 'zod';
const logSchema = z.object({
timestamp: z.date().default(() => new Date()),
});
logSchema.parse({});
// ➜ { timestamp: [current date object] }
Unlike static defaults, dynamic defaults in Zod produce a fresh value every time—making them ideal for audit logs, user sessions, or task schedulers.
Zod Discriminated Union Default: Smart Defaults for Union Schemas
In Zod, a discriminated union default lets you define fallback values for complex branching schemas. It’s especially helpful when working with conditionally shaped objects—like payment methods, notification channels, or component configs—where each option has its own structure.
By applying .default() at the object level, you can predefine which union branch should apply when input is missing. This reduces edge cases and simplifies fallback logic in dynamic forms or APIs.
Discriminated union defaults are powerful when you want a complete, ready-to-use shape even before user interaction.
Zod Default Error Messages: Customize Required & Invalid Feedback
While .default() in Zod prevents “required” errors by auto-filling undefined values, you may still want to customize error messages for invalid inputs or strict validations. Zod allows this using .refine() or .superRefine() along with .default() to provide clear, user-friendly feedback when a value is present but invalid.
This is especially useful in forms or APIs where default values solve missing inputs, but user-entered data still needs validation with helpful error messaging—like “Invalid email format” or “Must be a positive number.”
For full control, Zod also supports .describe() and schema-level .errorMap() to tailor messages globally.
Zod Default Value Not Working? Fix Common Issues
If your Zod default value isn’t working, the most common reason is that the input is null, not undefined. Zod’s .default() only triggers on undefined, so if you’re getting null, you’ll need to combine it with .nullable() or handle it manually.
Another issue is using .default() after .optional(), which can sometimes override expected behavior. Always apply .default() last in the chain to ensure it functions correctly.
Also, remember that .default() won’t fix invalid values—only missing ones. For incorrect types, use .catch() or add a .refine() for custom validation.
Zod Default Integration with React Hook Form
When using Zod with React Hook Form, .default() helps set fallback values inside your schema. However, to actually see those defaults in your form UI, you must also provide them via the defaultValues option in useForm()—Zod doesn’t auto-fill form inputs.
Use .default() to ensure validation consistency and defaultValues to control what’s rendered. This dual setup keeps your form logic clean and aligned with your schema.
Zod handles data parsing; React Hook Form handles UI rendering. Together, they offer powerful, type-safe form handling with reliable defaults.
Real-World Zod Default Example (Admin Dashboards & Internal Tools)
In admin dashboards or internal tools, data often comes from multiple sources—some complete, some partial. Let’s say your internal tool allows admins to generate reports, but certain parameters like limit, sortBy, or filters aren’t always set in the request.
Instead of forcing the frontend to fill everything, a Zod schema with .default() ensures the backend always processes predictable inputs. For example, you can default limit to 100, sortBy to “createdAt”, and filters to an empty object. This lets your service layer focus on logic, not edge-case handling.
In environments where speed and reliability matter—like auto-generated exports, logs, or audit trails—Zod defaults give your app structure without demanding complete user input.