{"record":{"id":"a09b277da74796bf","repo":"colinhacks/zod","slug":"cannot-specify-both-message-and-error-params","errorCode":null,"errorMessage":"Cannot specify both `message` and `error` params","messagePattern":"Cannot specify both `message` and `error` params","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/zod/src/v4/core/util.ts","lineNumber":523,"sourceCode":"        {\n          [k in keyof Omit<T, \"error\" | \"message\">]: T[k];\n        } & (\"error\" extends keyof T\n          ? {\n              error?: Exclude<T[\"error\"], string>;\n              // path?: PropertyKey[] | undefined;\n              // message?: string | undefined;\n            }\n          : unknown)\n      >\n    : never;\n\nexport function normalizeParams<T>(_params: T): Normalize<T> {\n  const params: any = _params;\n\n  if (!params) return {} as any;\n  if (typeof params === \"string\") return { error: () => params } as any;\n  if (params?.message !== undefined) {\n    if (params?.error !== undefined) throw new Error(\"Cannot specify both `message` and `error` params\");\n    params.error = params.message;\n  }\n  delete params.message;\n  if (typeof params.error === \"string\") return { ...params, error: () => params.error } as any;\n  return params;\n}\n\nexport function createTransparentProxy<T extends object>(getter: () => T): T {\n  let target: T;\n  return new Proxy(\n    {},\n    {\n      get(_, prop, receiver) {\n        target ??= getter();\n        return Reflect.get(target, prop, receiver);\n      },\n      set(_, prop, value, receiver) {\n        target ??= getter();","sourceCodeStart":505,"sourceCodeEnd":541,"githubUrl":"https://github.com/colinhacks/zod/blob/2d90846af918af9602e088812d63a035d47cdbe4/packages/zod/src/v4/core/util.ts#L505-L541","documentation":"Thrown by normalizeParams() when a single params object sets both `message` and `error`. Zod v4 unifies these into one `error` channel (a string or a function), so `message` is treated as a legacy alias that gets rewritten into `error`. Supplying both is ambiguous and rejected at parameter-normalization time, before any parsing happens. It fires across most schema/check/error-map APIs that accept a params object.","triggerScenarios":"Passing an object literal like `{ message: \"...\", error: (iss) => \"...\" }` to any API that runs its params through normalizeParams() — e.g. `z.string().check({ message: \"x\", error: () => \"y\" })`, custom error maps, or `.refine()` where a refine carries both keys. Also triggered by spreading two config sources that each define one of the keys.","commonSituations":"Migrating a v3 codebase where `message` was the only option while leaving a newly added v4 `error` fn in place; merging a base error-map with an override via spread; copy-pasting an example that mixed the two syntaxes; library wrappers that auto-inject `message` and then the user adds `error`.","solutions":["Pick one key: keep `error` (string or function) and delete the `message` property.","If you only need a static string, use `message` alone (it is rewritten to `error` internally) or pass the string directly as the params value.","When merging config objects, normalize each source to `error` before spreading so the combined object never carries both keys."],"exampleFix":"// before\nz.string().min(3, { message: \"too short\", error: (iss) => `bad: ${iss.input}` });\n\n// after (function form)\nz.string().min(3, { error: (iss) => `bad: ${iss.input}` });\n// after (static string form)\nz.string().min(3, { message: \"too short\" });","handlingStrategy":"validation","validationCode":"function assertNoMessageErrorConflict(params: unknown): void {\n  if (params && typeof params === 'object') {\n    const p = params as Record<string, unknown>;\n    if (p.message !== undefined && p.error !== undefined) {\n      throw new TypeError(\n        'params contains both `message` and `error`; keep only `error` (string or function)'\n      );\n    }\n  }\n}\n// run before any zod call that accepts a params object:\n// assertNoMessageErrorConflict(myParams);","typeGuard":"function hasMessageAndError(p: unknown): p is { message: unknown; error: unknown } {\n  if (!p || typeof p !== 'object') return false;\n  const o = p as Record<string, unknown>;\n  return o.message !== undefined && o.error !== undefined;\n}","tryCatchPattern":null,"preventionTips":["Standardize on the `error` key (string or function) across the codebase; treat `message` as legacy.","When merging error maps, normalize each source to `error` before spreading.","Add an ESLint rule or wrapper that rejects params objects containing both keys."],"tags":["api-misuse","params","migration","zod-v4"],"backgroundTag":null,"analyzedSha":"2d90846af918af9602e088812d63a035d47cdbe4","analyzedAt":"2026-08-11T01:21:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}