colinhacks/zod · error · Error
Invalid input to extend: expected a plain object
Error message
Invalid input to extend: expected a plain object
What it means
Thrown by extend() when the shape argument fails isPlainObject(). Zod requires the shape to be a plain object literal (prototype of Object.prototype or no modified constructor/prototype) so it can safely spread keys as field schemas. Class instances, arrays, Maps, null, and primitives are rejected before any keys are read.
Solutions
- Pass a plain object literal: `{ newField: z.string() }`.
- If your shape lives in a class/Map/wrapper, spread it into a fresh `{}` first: `schema.extend({ ...mapOrInstance })` after extracting the schema entries.
- Ensure the value is not an array, null, or a primitive — extend takes a key→schema map only.
Example fix
// before
const extra = new Map([['id', z.string()]]);
Base.extend(extra); // throws
// after
const extra = { id: z.string() };
Base.extend(extra); Defensive patterns
Strategy: type-guard
Validate before calling
function asShape(shape: unknown): Record<string, z.ZodType> {
if (!isPlainShape(shape)) {
throw new TypeError('extend() expects a plain object of key -> ZodType');
}
return shape as Record<string, z.ZodType>;
}
// base.extend(asShape(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
- Always pass a freshly-constructed object literal to `.extend()`.
- Coerce Maps/arrays with `Object.fromEntries(...)` before extending.
- Never pass a schema instance or a class instance where a shape map is expected.
When it happens
Trigger: Calling `schema.extend(SomeClassInstance)`, `schema.extend([...])`, `schema.extend(null)`, or passing a schema object, a Record subclass, or a Proxy whose constructor/prototype has been swapped. Also triggered by passing a `z.object(...)` result instead of its inner shape.
Common situations: Passing a class instance (e.g. a typed Record from a serializer) instead of a literal; accidentally feeding an array of [key, schema] pairs; handing in a schema instead of a shape map; using a custom object wrapper that mutates the prototype.
Related errors
- Cannot overwrite keys on object schemas containing…
- Invalid input to safeExtend: 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…
AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11).
Data as JSON: /api/errors/5c01fb2600917b9b.
Report an issue: GitHub.
Appendix: source
Thrown at packages/zod/src/v4/core/util.ts:661
if (!(key in currDef.shape)) {
throw new Error(`Unrecognized key: "${key}"`);
}
if (!(mask as any)[key]) continue;
delete newShape[key];
}
assignProp(this, "shape", newShape); // self-caching
return newShape;
},
checks: [],
});
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 };View on GitHub (pinned to 2d90846af9)