colinhacks/zod · error · Error
Invalid input to safeExtend: expected a plain object
Error message
Invalid input to safeExtend: expected a plain object
What it means
Thrown by safeExtend() when the shape argument fails the same isPlainObject() guard used by extend(). `.safeExtend()` is the refinement-safe variant of `.extend()` (it skips the check-overlap guard) but still requires a plain object literal as its shape argument. Class instances, arrays, Maps, null, and primitives are rejected.
Solutions
- Pass a plain object literal of key→schema pairs.
- Coerce wrappers (Map, class, Record subclass) into a plain object via spread or Object.fromEntries before calling `.safeExtend()`.
- Confirm the argument is not null, an array, or a primitive.
Example fix
// before const pairs: [string, z.ZodType][] = [['id', z.number()]]; Base.safeExtend(pairs); // throws // after Base.safeExtend(Object.fromEntries(pairs));
Defensive patterns
Strategy: type-guard
Validate before calling
function asSafeShape(shape: unknown): Record<string, z.ZodType> {
if (!isPlainShape(shape)) {
throw new TypeError('safeExtend() expects a plain object of key -> ZodType');
}
return shape as Record<string, z.ZodType>;
}
// base.safeExtend(asSafeShape(maybeShape)); Type guard
function isPlainShape(o: unknown): o is Record<string, z.ZodType> {
if (o === null || typeof o !== 'object' || Array.isArray(o)) return false;
const proto = Object.getPrototypeOf(o);
return proto === null || proto === Object.prototype;
} Prevention
- Pass only plain object literals (or Object.fromEntries results) to `.safeExtend()`.
- Reject arrays, Maps, and class instances at the boundary.
- Reuse the same isPlainShape guard you use for `.extend()`.
When it happens
Trigger: Calling `schema.safeExtend(classInstance)`, `schema.safeExtend([...])`, `schema.safeExtend(null)`, or any non-plain-object value. Same input contract as `.extend()`.
Common situations: Same as the extend() case: passing a typed Record, a schema object, an array of pairs, or a wrapper class instead of a literal shape map; migrating from `.extend()` to `.safeExtend()` without fixing an already-wrong argument type.
Related errors
- Invalid input to extend: expected a plain object
- Cannot overwrite keys on object schemas containing…
- .merge() cannot be used on object schemas containing…
- .omit() cannot be used on object schemas containing…
- .partial() cannot be used on object schemas containing…
AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11).
Data as JSON: /api/errors/a4eead5478eb1497.
Report an issue: GitHub.
Appendix: source
Thrown at packages/zod/src/v4/core/util.ts:689
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");
}
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 };View on GitHub (pinned to 2d90846af9)