Handle Nullable Fields in Zod Schemas from JSON
Learn how to generate Zod schemas with .nullable() for fields that can be null in JSON. Understand the difference between nullable and optional in Zod.
Detailed Explanation
Nullable Fields in Zod
JSON explicitly supports null as a value. Zod's .nullable() modifier allows a field to accept null in addition to its base type.
Example JSON
{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"deletedAt": null,
"lastLoginAt": null,
"avatar": null
}
Generated Zod Schema (with nullable toggle)
import { z } from "zod";
const userSchema = z.object({
id: z.number().int().nullable(),
name: z.string().nullable(),
email: z.string().nullable(),
deletedAt: z.null().nullable(),
lastLoginAt: z.null().nullable(),
avatar: z.null().nullable(),
});
type User = z.infer<typeof userSchema>;
Practical Pattern: Nullable Dates
A common pattern in APIs is date fields that are null when not yet set:
const taskSchema = z.object({
id: z.number(),
title: z.string(),
createdAt: z.string(),
completedAt: z.string().nullable(), // null when not completed
deletedAt: z.string().nullable(), // null when not deleted
});
nullable() vs null()
z.null()only accepts the valuenull— nothing else.z.string().nullable()accepts either a string or null.- This distinction matters when a field is always null in your sample but could be a string in production.
Combining with .optional()
// Field can be: string, null, or absent entirely
z.string().nullable().optional()
// Field can be: string or absent (but not null)
z.string().optional()
// Field can be: string or null (but must be present)
z.string().nullable()
Nullish Shorthand
Zod provides .nullish() as shorthand for .nullable().optional():
// These are equivalent:
z.string().nullable().optional()
z.string().nullish()
Use .nullish() when a field can be null, undefined, or the base type — a common pattern with SQL databases where columns can be NULL and ORM queries may omit unselected fields.
Use Case
Your database returns user records where soft-deleted rows have a non-null deletedAt timestamp, and you need a schema that correctly validates both active (null) and deleted (string) states.