colinhacks/zod · error · Error

.partial() cannot be used on object schemas containing refin

Error message

.partial() cannot be used on object schemas containing refinements

What it means

Thrown by `.partial()` when the target object schema has refinement checks (guard at util.ts:726-728). Making fields optional would change the input shape that refinements validate against, risking silent invalidation, so `.partial()` refuses. There is no `safePartial` variant — you must restructure.

Source

Thrown at packages/zod/src/v4/core/util.ts:729

    get catchall() {
      return b._zod.def.catchall;
    },
    checks: b._zod.def.checks ?? [],
  });

  return clone(a, def) as any;
}

export function partial(
  Class: SchemaClass<schemas.$ZodOptional> | null,
  schema: schemas.$ZodObject,
  mask: object | undefined
): any {
  const currDef = schema._zod.def;
  const checks = currDef.checks;
  const hasChecks = checks && checks.length > 0;
  if (hasChecks) {
    throw new Error(".partial() cannot be used on object schemas containing refinements");
  }

  const def = mergeDefs(schema._zod.def, {
    get shape() {
      const oldShape = schema._zod.def.shape;
      const shape: Writeable<schemas.$ZodShape> = { ...oldShape };

      if (mask) {
        for (const key in mask) {
          if (!(key in oldShape)) {
            throw new Error(`Unrecognized key: "${key}"`);
          }
          if (!(mask as any)[key]) continue;
          // if (oldShape[key]!._zod.optin === "optional") continue;
          shape[key] = Class
            ? new Class({
                type: "optional",
                innerType: oldShape[key]!,

View on GitHub (pinned to 912f0f51b0)

Solutions

  1. Move refinements off the base object, call `.partial()` on the bare object, then reattach refinements to the result.
  2. Define the create schema without cross-field checks, derive `.partial()` for updates, and add refinements per variant.
  3. Use a separate update schema entirely instead of deriving with `.partial()`.

Example fix

// before
const User = z.object({ id: z.string(), email: z.string() }).refine(d => d.id !== d.email);
const Update = User.partial(); // throws

// after
const Base = z.object({ id: z.string(), email: z.string() });
const Update = Base.partial().refine(d => d.id !== d.email);
Defensive patterns

Strategy: type-guard

Validate before calling

function hasRefinements(s: z.core.$ZodObject): boolean {
  return !!s._zod.def.checks?.length;
}
if (hasRefinements(User)) {
  // restructure: bare object -> partial -> reattach refine
}

Type guard

function isRefinementFree(s: z.core.$ZodObject): boolean {
  return !s._zod.def.checks?.length;
}

Prevention

When it happens

Trigger: Calling `Schema.partial()` or `Schema.partial({ field: true })` on a schema that has `.refine()/.superRefine()` attached.

Common situations: Building an update/patch schema from a validated create schema via `.partial()`; sharing a single source-of-truth object with refinements across POST/PATCH handlers.

Related errors


AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03). Data as JSON: /data/errors/f7b87486aa94bc94.json. Report an issue: GitHub.