{"id":"bf96072a776a8fe6","repo":"colinhacks/zod","slug":"merge-cannot-be-used-on-object-schemas-containi","errorCode":null,"errorMessage":".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.","messagePattern":"\\.merge\\(\\) cannot be used 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":703,"sourceCode":"}\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  }\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 merge(a: schemas.$ZodObject, b: schemas.$ZodObject): any {\n  if (a._zod.def.checks?.length) {\n    throw new Error(\".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.\");\n  }\n  const def = mergeDefs(a._zod.def, {\n    get shape() {\n      const _shape = { ...a._zod.def.shape, ...b._zod.def.shape };\n      assignProp(this, \"shape\", _shape); // self-caching\n      return _shape;\n    },\n    get catchall() {\n      return b._zod.def.catchall;\n    },\n    checks: b._zod.def.checks ?? [],\n  });\n\n  return clone(a, def) as any;\n}\n\nexport function partial(\n  Class: SchemaClass<schemas.$ZodOptional> | null,","sourceCodeStart":685,"sourceCodeEnd":721,"githubUrl":"https://github.com/colinhacks/zod/blob/912f0f51b0ced654d0069741e7160834dca742ee/packages/zod/src/v4/core/util.ts#L685-L721","documentation":"Thrown by `.merge()` when the first argument (`a`) has refinement checks (`a._zod.def.checks?.length` at util.ts:702). `.merge()` would silently drop or relocate those refinements during the shape combine, so it refuses and points to `.safeExtend()`, which is the intended refinement-safe composition tool.","triggerScenarios":"Calling `ValidatedSchema.merge(OtherSchema)` where `ValidatedSchema` is `z.object({...}).refine(...)` or has any check in `def.checks`.","commonSituations":"Composing a base object with cross-field validation against a second object schema; sharing validated base schemas across endpoints and merging in per-endpoint fields; migrating from v3 where `.merge()` was more permissive.","solutions":["Use `a.safeExtend(b.shape)` (or spread `b._zod.def.shape` into a plain object) as the suggested workaround.","Pull refinements off `a` and reattach them after `.merge()`.","Define `a` as the bare object and attach refinements only on the final composed schema."],"exampleFix":"// before\nconst Merged = Validated.merge(Extra);\n\n// after\nconst Merged = Validated.safeExtend(Extra._zod.def.shape);","handlingStrategy":"validation","validationCode":"function hasRefinements(s: z.core.$ZodObject): boolean {\n  return !!s._zod.def.checks?.length;\n}\nif (hasRefinements(a)) {\n  // merge manually via safeExtend\n  const merged = a.safeExtend(b._zod.def.shape);\n}","typeGuard":"function isRefinementFree(s: z.core.$ZodObject): boolean {\n  return !s._zod.def.checks?.length;\n}","tryCatchPattern":"try { const Out = a.merge(b); }\ncatch (e) {\n  if (e instanceof Error && /merge.*safeExtend/.test(e.message)) {\n    const Out = a.safeExtend(b._zod.def.shape);\n  } else throw e;\n}","preventionTips":["Attach refinements after composition, not on the base schema being merged.","Standardize on `.safeExtend(b.shape)` for refinement-safe merges."],"tags":["zod","object-schema","merge","refinement"],"analyzedSha":"912f0f51b0ced654d0069741e7160834dca742ee","analyzedAt":"2026-08-03T17:41:55.908Z","schemaVersion":2}