colinhacks/zod · error · Error
.merge() cannot be used on object schemas containing refinem
Error message
.merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.
What it means
Thrown by `.merge()` when the first argument (`a`) has refinement checks (`a._zod.def.checks?.length` at util.ts:702). `.merge()` would silently drop or relocate those refinements during the shape combine, so it refuses and points to `.safeExtend()`, which is the intended refinement-safe composition tool.
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 912f0f51b0)
Solutions
- Use `a.safeExtend(b.shape)` (or spread `b._zod.def.shape` into a plain object) as the suggested workaround.
- Pull refinements off `a` and reattach them after `.merge()`.
- Define `a` as the bare object and attach refinements only on the final composed schema.
Example fix
// before const Merged = Validated.merge(Extra); // after const Merged = Validated.safeExtend(Extra._zod.def.shape);
Defensive patterns
Strategy: validation
Validate before calling
function hasRefinements(s: z.core.$ZodObject): boolean {
return !!s._zod.def.checks?.length;
}
if (hasRefinements(a)) {
// merge manually via safeExtend
const merged = a.safeExtend(b._zod.def.shape);
} Type guard
function isRefinementFree(s: z.core.$ZodObject): boolean {
return !s._zod.def.checks?.length;
} Try / catch
try { const Out = a.merge(b); }
catch (e) {
if (e instanceof Error && /merge.*safeExtend/.test(e.message)) {
const Out = a.safeExtend(b._zod.def.shape);
} else throw e;
} Prevention
- Attach refinements after composition, not on the base schema being merged.
- Standardize on `.safeExtend(b.shape)` for refinement-safe merges.
When it happens
Trigger: Calling `ValidatedSchema.merge(OtherSchema)` where `ValidatedSchema` is `z.object({...}).refine(...)` or has any check in `def.checks`.
Common situations: Composing a base object with cross-field validation against a second object schema; sharing validated base schemas across endpoints and merging in per-endpoint fields; migrating from v3 where `.merge()` was more permissive.
Related errors
- .omit() cannot be used on object schemas containing refineme
- Cannot overwrite keys on object schemas containing refinemen
- .partial() cannot be used on object schemas containing refin
- Unrecognized key: "${key}"
- Invalid input to extend: expected a plain object
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/bf96072a776a8fe6.json.
Report an issue: GitHub.