colinhacks/zod · error · Error
Cannot overwrite keys on object schemas containing refinemen
Error message
Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.
What it means
Thrown by `.extend()` when the schema has refinement checks AND the new shape tries to overwrite a key that already exists (guard at util.ts:666-673). Overwriting a field that a refinement depends on would silently break cross-field validation, so the library refuses and redirects to `.safeExtend()`, which intentionally bypasses the check.
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 912f0f51b0)
Solutions
- Use `.safeExtend({ key: newSchema })` if you intentionally accept the refinement-risk tradeoff.
- Move the refinement off the base schema and reattach it after `.extend()`.
- Define the base object without refinements, extend it, then layer `.refine()` on each variant.
Example fix
// before
const Base = z.object({ id: z.string() }).refine(d => d.id.length > 0);
const Wide = Base.extend({ id: z.string().uuid() }); // throws
// after
const Wide = Base.safeExtend({ id: z.string().uuid() }); Defensive patterns
Strategy: validation
Validate before calling
function hasOverlaps(s: z.core.$ZodObject, shape: Record<string, unknown>): boolean {
const existing = s._zod.def.shape;
return Object.keys(shape).some(k => Object.getOwnPropertyDescriptor(existing, k) !== undefined);
}
if (hasRefinements(Schema) && hasOverlaps(Schema, newShape)) {
// use safeExtend intentionally
} Type guard
function isRefinementFree(s: z.core.$ZodObject): boolean {
return !s._zod.def.checks?.length;
} Try / catch
try { Out = Schema.extend(newShape); }
catch (e) {
if (e instanceof Error && /safeExtend/.test(e.message)) Out = Schema.safeExtend(newShape);
else throw e;
} Prevention
- Keep refinements off base schemas that need to be extended.
- If you knowingly accept the risk, call `.safeExtend()` upfront instead of `.extend()`.
When it happens
Trigger: Calling `Schema.extend({ existingKey: z.string() })` where `Schema` was built with `.refine()/.superRefine()` touching `existingKey`.
Common situations: Overriding a field type on a validated schema (e.g. widening `id` from `z.string()` to `z.string().uuid()`); reusing a base schema with refinements and customizing fields per endpoint; inheriting a shared validated schema.
Related errors
- .omit() cannot be used on object schemas containing refineme
- Invalid input to extend: expected a plain object
- .merge() cannot be used on object schemas containing refinem
- .partial() cannot be used on object schemas containing refin
- Unrecognized key: "${key}"
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/770e059bcd82d0fa.json.
Report an issue: GitHub.