{"id":"5c01fb2600917b9b","repo":"colinhacks/zod","slug":"invalid-input-to-extend-expected-a-plain-object","errorCode":null,"errorMessage":"Invalid input to extend: expected a plain object","messagePattern":"Invalid input to extend: expected a plain object","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/zod/src/v4/core/util.ts","lineNumber":661,"sourceCode":"        if (!(key in currDef.shape)) {\n          throw new Error(`Unrecognized key: \"${key}\"`);\n        }\n        if (!(mask as any)[key]) continue;\n\n        delete newShape[key];\n      }\n      assignProp(this, \"shape\", newShape); // self-caching\n      return newShape;\n    },\n    checks: [],\n  });\n\n  return clone(schema, def);\n}\n\nexport function extend(schema: schemas.$ZodObject, shape: schemas.$ZodShape): any {\n  if (!isPlainObject(shape)) {\n    throw new Error(\"Invalid input to extend: expected a plain object\");\n  }\n\n  const checks = schema._zod.def.checks;\n  const hasChecks = checks && checks.length > 0;\n  if (hasChecks) {\n    // Only throw if new shape overlaps with existing shape\n    // Use getOwnPropertyDescriptor to check key existence without accessing values\n    const existingShape = schema._zod.def.shape;\n    for (const key in shape) {\n      if (Object.getOwnPropertyDescriptor(existingShape, key) !== undefined) {\n        throw new Error(\"Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.\");\n      }\n    }\n  }\n\n  const def = mergeDefs(schema._zod.def, {\n    get shape() {\n      const _shape = { ...schema._zod.def.shape, ...shape };","sourceCodeStart":643,"sourceCodeEnd":679,"githubUrl":"https://github.com/colinhacks/zod/blob/912f0f51b0ced654d0069741e7160834dca742ee/packages/zod/src/v4/core/util.ts#L643-L679","documentation":"Thrown by `.extend()` when the argument is not a plain object — checked via `isPlainObject(shape)` at util.ts:660. `.extend()` needs a literal shape map of `ZodType` values; passing arrays, class instances, `null`, `undefined`, or anything with a non-standard prototype is rejected upfront. This prevents silent spread-merge of unexpected inputs into the schema's shape.","triggerScenarios":"Calling `schema.extend(someArray)` or `schema.extend(someZodType)` or `schema.extend(JSON.parse(json))` where the result is not a plain object literal of field schemas.","commonSituations":"Dynamically building a shape from `Object.values()` (array) and passing it directly; passing a single `z.string()` instead of `{ field: z.string() }`; passing a `Map` or other iterable; deserializing shapes from JSON without reconstructing Zod types.","solutions":["Wrap the input as an object literal: `schema.extend({ field: z.string() })` rather than `schema.extend(z.string())`.","If building dynamically from entries, convert with `Object.fromEntries(entries)` before passing.","If you intended to merge two object schemas, use `.merge(other)` instead of `.extend(other)`."],"exampleFix":"// before\nconst Extended = User.extend(z.string());\n\n// after\nconst Extended = User.extend({ nickname: z.string() });","handlingStrategy":"type-guard","validationCode":"function isPlainShape(v: unknown): v is Record<string, z.core.$ZodType> {\n  return Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null;\n}\nif (!isPlainShape(arg)) throw new Error('shape must be a plain object');\nconst Out = schema.extend(arg);","typeGuard":"function isZodShape(v: unknown): v is z.core.$ZodShape {\n  return typeof v === 'object' && v !== null &&\n    (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null) &&\n    Object.values(v).every(x => x && typeof x === 'object' && '_zod' in x);\n}","tryCatchPattern":null,"preventionTips":["Always pass object literals `{ field: z.X() }`, not single schemas or arrays.","Build dynamic shapes via `Object.fromEntries(entries)`."],"tags":["zod","object-schema","extend","type-error"],"analyzedSha":"912f0f51b0ced654d0069741e7160834dca742ee","analyzedAt":"2026-08-03T17:41:55.908Z","schemaVersion":2}