{"record":{"id":"770e059bcd82d0fa","repo":"colinhacks/zod","slug":"cannot-overwrite-keys-on-object-schemas-containing","errorCode":null,"errorMessage":"Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.","messagePattern":"Cannot overwrite keys on object schemas containing refinements\\. Use `\\.safeExtend\\(\\)` instead\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/zod/src/v4/core/util.ts","lineNumber":672,"sourceCode":"  });\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 };\n      assignProp(this, \"shape\", _shape); // self-caching\n      return _shape;\n    },\n  });\n  return clone(schema, def) as any;\n}\n\nexport function safeExtend(schema: schemas.$ZodObject, shape: schemas.$ZodShape): any {\n  if (!isPlainObject(shape)) {\n    throw new Error(\"Invalid input to safeExtend: expected a plain object\");\n  }","sourceCodeStart":654,"sourceCodeEnd":690,"githubUrl":"https://github.com/colinhacks/zod/blob/2d90846af918af9602e088812d63a035d47cdbe4/packages/zod/src/v4/core/util.ts#L654-L690","documentation":"Thrown by extend() only when the source object carries refinements AND the new shape would overwrite an existing key. Overwriting a field while object-level checks are present would silently change what the refinements validate, so Zod refuses and points to `.safeExtend()`, which intentionally bypasses the check-overlap guard. Purely additive keys on a refined object do NOT trigger this.","triggerScenarios":"Calling `UserWithRefine.extend({ email: z.number() })` where `email` already exists and `UserWithRefine` has `.refine()`/`.check()`. The guard iterates the new shape and throws on the first key found in the existing shape via `Object.getOwnPropertyDescriptor`.","commonSituations":"Overriding an inherited/base field on a refined object (e.g. widening `id` from string to string|number); composing schemas where a base carries invariants; refactoring a field type on a schema that gained a refinement upstream.","solutions":["Use `.safeExtend({ ...overwrittenKey })` if you intentionally accept that object-level refinements now validate against the new field type.","Drop or relocate the refinement so `.extend()` can overwrite freely.","Build a new object schema from scratch combining the wanted fields and re-apply the refinement explicitly."],"exampleFix":"// before\nconst Base = z.object({ id: z.string() }).refine(v => v.id.length > 0);\nconst Extended = Base.extend({ id: z.number() }); // throws\n\n// after — explicit, refinement-aware overwrite\nconst Extended = Base.safeExtend({ id: z.number() });","handlingStrategy":"validation","validationCode":"function canExtendOverwrite(schema: z.ZodObject, shape: Record<string, unknown>): boolean {\n  const checks = (schema as any)._zod?.def?.checks;\n  if (!checks || checks.length === 0) return true;\n  const existing = (schema as any)._zod.def.shape;\n  for (const k of Object.keys(shape)) {\n    if (Object.prototype.hasOwnProperty.call(existing, k)) return false;\n  }\n  return true;\n}\n// if (!canExtendOverwrite(Base, { id: z.number() })) Base = Base.safeExtend({ id: z.number() });","typeGuard":"function extendWouldOverwrite(schema: z.ZodObject, shape: Record<string, unknown>): boolean {\n  const existing = (schema as any)._zod.def.shape;\n  return Object.keys(shape).some((k) => Object.prototype.hasOwnProperty.call(existing, k));\n}","tryCatchPattern":null,"preventionTips":["When overwriting fields on a refined object, default to `.safeExtend()` and document why.","Keep refinement-bearing bases additive-only unless you intentionally accept the semantics change.","Centralize schema composition in one module so overwrite decisions are reviewable."],"tags":["schema-transformation","extend","refinement","object-schema","zod-v4"],"backgroundTag":null,"analyzedSha":"2d90846af918af9602e088812d63a035d47cdbe4","analyzedAt":"2026-08-11T01:21:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}