colinhacks/zod · error · Error
Cannot overwrite keys on object schemas containing…
Error message
Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.
What it means
Thrown by extend() only when the source object carries refinements AND the new shape would overwrite an existing key. Overwriting a field while object-level checks are present would silently change what the refinements validate, so Zod refuses and points to `.safeExtend()`, which intentionally bypasses the check-overlap guard. Purely additive keys on a refined object do NOT trigger this.
Solutions
- Use `.safeExtend({ ...overwrittenKey })` if you intentionally accept that object-level refinements now validate against the new field type.
- Drop or relocate the refinement so `.extend()` can overwrite freely.
- Build a new object schema from scratch combining the wanted fields and re-apply the refinement explicitly.
Example fix
// before
const Base = z.object({ id: z.string() }).refine(v => v.id.length > 0);
const Extended = Base.extend({ id: z.number() }); // throws
// after — explicit, refinement-aware overwrite
const Extended = Base.safeExtend({ id: z.number() }); Defensive patterns
Strategy: validation
Validate before calling
function canExtendOverwrite(schema: z.ZodObject, shape: Record<string, unknown>): boolean {
const checks = (schema as any)._zod?.def?.checks;
if (!checks || checks.length === 0) return true;
const existing = (schema as any)._zod.def.shape;
for (const k of Object.keys(shape)) {
if (Object.prototype.hasOwnProperty.call(existing, k)) return false;
}
return true;
}
// if (!canExtendOverwrite(Base, { id: z.number() })) Base = Base.safeExtend({ id: z.number() }); Type guard
function extendWouldOverwrite(schema: z.ZodObject, shape: Record<string, unknown>): boolean {
const existing = (schema as any)._zod.def.shape;
return Object.keys(shape).some((k) => Object.prototype.hasOwnProperty.call(existing, k));
} Prevention
- When overwriting fields on a refined object, default to `.safeExtend()` and document why.
- Keep refinement-bearing bases additive-only unless you intentionally accept the semantics change.
- Centralize schema composition in one module so overwrite decisions are reviewable.
When it happens
Trigger: Calling `UserWithRefine.extend({ email: z.number() })` where `email` already exists and `UserWithRefine` has `.refine()`/`.check()`. The guard iterates the new shape and throws on the first key found in the existing shape via `Object.getOwnPropertyDescriptor`.
Common situations: Overriding an inherited/base field on a refined object (e.g. widening `id` from string to string|number); composing schemas where a base carries invariants; refactoring a field type on a schema that gained a refinement upstream.
Related errors
- Invalid input to extend: expected a plain object
- .merge() cannot be used 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…
AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11).
Data as JSON: /api/errors/770e059bcd82d0fa.
Report an issue: GitHub.
Appendix: source
Thrown at packages/zod/src/v4/core/util.ts:672
});
return clone(schema, def);
}
export function extend(schema: schemas.$ZodObject, shape: schemas.$ZodShape): any {
if (!isPlainObject(shape)) {
throw new Error("Invalid input to extend: expected a plain object");
}
const checks = schema._zod.def.checks;
const hasChecks = checks && checks.length > 0;
if (hasChecks) {
// Only throw if new shape overlaps with existing shape
// Use getOwnPropertyDescriptor to check key existence without accessing values
const existingShape = schema._zod.def.shape;
for (const key in shape) {
if (Object.getOwnPropertyDescriptor(existingShape, key) !== undefined) {
throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
}
}
}
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 safeExtend(schema: schemas.$ZodObject, shape: schemas.$ZodShape): any {
if (!isPlainObject(shape)) {
throw new Error("Invalid input to safeExtend: expected a plain object");
}View on GitHub (pinned to 2d90846af9)