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
- Use `a.safeExtend(b._zod.def.shape)` to merge shapes while keeping `a`'s refinements explicit.
- Drop the refinement from `a` before merging and re-apply a combined refinement to the result.
- 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
- Prefer `.safeExtend(b.shape)` when merging a refined base with another object.
- Keep the left operand of `.merge()` refinement-free; attach refinements after merging.
- Document which schemas are merge-safe vs invariant-bearing.
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
- Cannot overwrite keys on object schemas containing…
- .omit() cannot be used on object schemas containing…
- .partial() cannot be used on object schemas containing…
- .pick() cannot be used on object schemas containing…
- Invalid input to extend: expected a plain object
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)