colinhacks/zod · error · Error

.merge() cannot be used on object schemas containing…

Error message

.merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.

What it means

Thrown by merge() when the first object argument (`a`) has any refinements in its checks array. `.merge()` rebuilds the schema from `a`'s def and replaces checks with `b`'s checks, which would silently discard `a`'s refinements. The error message redirects to `.safeExtend()`, which preserves both sides' definitions without dropping checks.

Solutions

  1. Use `a.safeExtend(b._zod.def.shape)` to merge shapes while keeping `a`'s refinements explicit.
  2. Drop the refinement from `a` before merging and re-apply a combined refinement to the result.
  3. Rebuild the merged schema from scratch with `z.object({ ...aFields, ...bFields }).refine(...)`.

Example fix

// before
const A = z.object({ x: z.string() }).refine(v => v.x.length > 0);
const B = z.object({ y: z.number() });
const M = A.merge(B); // throws

// after
const M = A.safeExtend(B._zod.def.shape);
Defensive patterns

Strategy: validation

Validate before calling

function canMerge(a: z.ZodObject): boolean {
  const checks = (a as any)?._zod?.def?.checks;
  return !checks || checks.length === 0;
}
// if (!canMerge(A)) A.safeExtend(B._zod.def.shape); else A.merge(B);

Type guard

function hasObjectRefinements(schema: z.ZodObject): boolean {
  const checks = (schema as any)?._zod?.def?.checks;
  return Array.isArray(checks) && checks.length > 0;
}

Prevention

When it happens

Trigger: Calling `Refined.merge(Other)` where `Refined` was built with `.refine()` or `.check()` on the object node. Only the left-hand schema's checks are guarded; `b`'s checks are carried over into the merged def.

Common situations: Combining a base schema that carries invariants with a mixin schema; merging a domain entity with an audit-trail schema; refactoring a flat schema into two halves when one half already has a refinement.

Related errors


AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11). Data as JSON: /api/errors/bf96072a776a8fe6. Report an issue: GitHub.

Appendix: source

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

}

export function safeExtend(schema: schemas.$ZodObject, shape: schemas.$ZodShape): any {
  if (!isPlainObject(shape)) {
    throw new Error("Invalid input to safeExtend: expected a plain object");
  }
  const def = mergeDefs(schema._zod.def, {
    get shape() {
      const _shape = { ...schema._zod.def.shape, ...shape };
      assignProp(this, "shape", _shape); // self-caching
      return _shape;
    },
  });
  return clone(schema, def) as any;
}

export function merge(a: schemas.$ZodObject, b: schemas.$ZodObject): any {
  if (a._zod.def.checks?.length) {
    throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");
  }
  const def = mergeDefs(a._zod.def, {
    get shape() {
      const _shape = { ...a._zod.def.shape, ...b._zod.def.shape };
      assignProp(this, "shape", _shape); // self-caching
      return _shape;
    },
    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,

View on GitHub (pinned to 2d90846af9)