Zod Deep Partial Utility
zod-deep-partial is a lightweight utility package designed to recursively make all properties within a given Zod schema optional. The package is currently at version 1.4.4 and is actively maintained, indicated by recent updates to support Zod v4 and ongoing development on its GitHub repository. Its primary differentiator from Zod's native `.partial()` method is its deep, recursive application of optionality, making it ideal for scenarios like patch updates or handling partially complete data structures. It boasts comprehensive support for a wide array of Zod types, including nested objects, arrays, unions, discriminated unions, intersections, and various transformations, all while maintaining Zod's robust type inference. Crucially, `zod-deep-partial` maintains a minimal footprint by having zero direct dependencies, relying solely on `zod` as a peer dependency.
Common errors
-
TypeError: zodDeepPartial is not a function
cause This error typically occurs when attempting to use `zodDeepPartial` in a CommonJS (`require`) environment where it's incorrectly imported, or when destructuring a named export incorrectly.fixEnsure you are using ES module `import` syntax (`import { zodDeepPartial } from 'zod-deep-partial';`) in an environment that supports it. If you must use CommonJS, ensure proper interoperability or transpile your code, or try `const { zodDeepPartial } = require('zod-deep-partial');`. -
ZodError: Required
cause Even after applying `zodDeepPartial`, some Zod schema types or custom refinements might still enforce 'required' behavior if not handled correctly by the deep partial logic, or if a specific Zod version causes an edge case.fixReview your original Zod schema for any `.nonoptional()` or `.required()` calls that might override the deep partial behavior. If the issue persists with basic types, check for a compatibility issue with your `zod` version and `zod-deep-partial` version.
Warnings
- breaking `zod-deep-partial` versions 1.2.0 and later require Zod v4. If your project is still using Zod v3, you must use `zod-deep-partial` versions prior to 1.2.0 (e.g., v1.1.0 or earlier). Zod v4 introduced significant breaking changes in its API and internal types, making it incompatible with older versions of `zod-deep-partial`.
- gotcha Zod's native `.partial()` method only makes properties optional at the top level of an object schema. Many developers mistakenly expect it to apply recursively. `zodDeepPartial` is specifically designed to address this by recursively applying optionality to all nested properties within a schema.
- gotcha While `zodDeepPartial` makes properties optional, it does not alter the fundamental type constraints of the properties themselves. For example, a `z.string().email()` property, even if optional, will still fail validation if a non-email string is provided for it (rather than being omitted).
Install
-
npm install zod-deep-partial -
yarn add zod-deep-partial -
pnpm add zod-deep-partial
Imports
- zodDeepPartial
const { zodDeepPartial } = require('zod-deep-partial');import { zodDeepPartial } from 'zod-deep-partial'; - z
const z = require('zod');import { z } from 'zod';
Quickstart
import { z } from "zod";
import { zodDeepPartial } from "zod-deep-partial";
// 1. Define your base schema
const userSchema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email format"),
profile: z.object({
bio: z.string().max(200, "Bio too long"),
avatar: z.string().url("Invalid URL format").nullable().optional(),
}),
tags: z.array(z.string().min(1, "Tag cannot be empty")),
settings: z.record(z.string(), z.boolean()),
createdAt: z.date(),
});
// 2. Create the deep partial schema
const partialUserSchema = zodDeepPartial(userSchema);
// 3. Use the partial schema for validation and observe type inference
// All of these are now valid:
try {
partialUserSchema.parse({});
console.log("Empty object valid.");
} catch (error) {
console.error("Validation failed for empty object:", error);
}
try {
partialUserSchema.parse({ name: "John Doe" });
console.log("Object with name valid.");
} catch (error) {
console.error("Validation failed for object with name:", error);
}
try {
partialUserSchema.parse({ profile: {} });
console.log("Object with empty profile valid.");
} catch (error) {
console.error("Validation failed for object with empty profile:", error);
}
try {
partialUserSchema.parse({ profile: { bio: "A developer" } });
console.log("Object with partial profile valid.");
} catch (error) {
console.error("Validation failed for object with partial profile:", error);
}
try {
partialUserSchema.parse({ tags: ["developer"] });
console.log("Object with tags valid.");
} catch (error) {
console.error("Validation failed for object with tags:", error);
}
// Example of what still fails (underlying type validations still apply if present)
try {
partialUserSchema.parse({ email: "invalid-email" }); // Should fail because 'email' is present but malformed
console.log("This should not be valid but passed (check expected behavior).");
} catch (error: any) {
console.log("Expected validation failure for invalid email:", error.issues[0].message);
}
// Type inference is preserved correctly
type PartialUser = z.infer<typeof partialUserSchema>;
// Expected type:
/*
{
name?: string | undefined;
email?: string | undefined;
profile?: {
bio?: string | undefined;
avatar?: string | null | undefined;
} | undefined;
tags?: (string | undefined)[] | undefined;
settings?: Record<string, boolean> | undefined;
createdAt?: Date | undefined;
}
*/
console.log("Type inference for PartialUser looks correct in IDE.");