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 argument is not a plain object (guard at util.ts:688, same `isPlainObject` check as `.extend()`). `.safeExtend()` is the refinement-tolerant variant of `.extend()`, but it still requires a literal shape map of `ZodType` values.
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 912f0f51b0)
Solutions
- Pass a plain object literal of field schemas: `schema.safeExtend({ field: z.string() })`.
- If building dynamically, use `Object.fromEntries()` to materialize a plain object first.
- Add a TypeScript annotation `const shape: z.core.$ZodShape = {...}` to surface shape errors at compile time.
Example fix
// before
const Extended = User.safeExtend(SomeSchema);
// after
const Extended = User.safeExtend({ field: SomeSchema }); Defensive patterns
Strategy: type-guard
Validate before calling
function isPlainShape(v: unknown): v is Record<string, z.core.$ZodType> {
return typeof v === 'object' && v !== null &&
[Object.prototype, null].includes(Object.getPrototypeOf(v));
}
if (!isPlainShape(arg)) throw new Error('safeExtend needs a plain object');
const Out = schema.safeExtend(arg); Type guard
function isZodShape(v: unknown): v is z.core.$ZodShape {
return typeof v === 'object' && v !== null &&
[Object.prototype, null].includes(Object.getPrototypeOf(v)) &&
Object.values(v).every(x => x && typeof x === 'object' && '_zod' in x);
} Prevention
- Pass `{ field: schema }` literals only.
- Annotate dynamic shapes with `z.core.$ZodShape` to surface type errors.
When it happens
Trigger: Calling `schema.safeExtend(null)`, `schema.safeExtend(array)`, or passing a non-plain-object value where a shape record is expected.
Common situations: Migrating a `.extend()` call to `.safeExtend()` to bypass refinement guards but keeping a malformed argument; passing a single schema instance instead of `{ key: schema }`; deserializing shapes from config without reconstruction.
Related errors
- Invalid input to extend: expected a plain object
- Unrecognized key: "${key}"
- .omit() cannot be used on object schemas containing refineme
- Cannot overwrite keys on object schemas containing refinemen
- .merge() cannot be used on object schemas containing refinem
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/a4eead5478eb1497.json.
Report an issue: GitHub.