colinhacks/zod · error · Error
Cannot specify both `message` and `error` params
Error message
Cannot specify both `message` and `error` params
What it means
Thrown by `normalizeParams` when a check/error parameter object specifies both `message` and `error`. These are aliases: `message` is the user-friendly string form, `error` is the full callback form (`(iss) => string`). Specifying both is ambiguous about which wins, so Zod rejects it. This fires at definition time whenever params are normalized (on any check, refinement, or schema params).
Source
Thrown at packages/zod/src/v4/core/util.ts:523
{
[k in keyof Omit<T, "error" | "message">]: T[k];
} & ("error" extends keyof T
? {
error?: Exclude<T["error"], string>;
// path?: PropertyKey[] | undefined;
// message?: string | undefined;
}
: unknown)
>
: never;
export function normalizeParams<T>(_params: T): Normalize<T> {
const params: any = _params;
if (!params) return {} as any;
if (typeof params === "string") return { error: () => params } as any;
if (params?.message !== undefined) {
if (params?.error !== undefined) throw new Error("Cannot specify both `message` and `error` params");
params.error = params.message;
}
delete params.message;
if (typeof params.error === "string") return { ...params, error: () => params.error } as any;
return params;
}
export function createTransparentProxy<T extends object>(getter: () => T): T {
let target: T;
return new Proxy(
{},
{
get(_, prop, receiver) {
target ??= getter();
return Reflect.get(target, prop, receiver);
},
set(_, prop, value, receiver) {
target ??= getter();View on GitHub (pinned to 912f0f51b0)
Solutions
- Use only one: `message` (string) OR `error` (function), never both.
- If you have a base params object, delete the stale key before adding the new one: `const p = { ...base }; delete p.error; p.message = 'x';`.
- Search the offending call site for `message:` and `error:` appearing together and remove one.
Example fix
// before
z.string().min(3, { message: 'too short', error: () => 'too short' });
// after
z.string().min(3, { message: 'too short' });
// or
z.string().min(3, { error: () => 'too short' }); Defensive patterns
Strategy: validation
Validate before calling
function assertSingleParam(p) {
if (p && p.message !== undefined && p.error !== undefined) {
throw new Error('pass either message or error, not both');
}
} Type guard
function hasBothMessageAndError(p): boolean {
return !!p && p.message !== undefined && p.error !== undefined;
} Try / catch
try {
schema.min(1, { message, error });
} catch (e) {
if (e instanceof Error && e.message === 'Cannot specify both `message` and `error` params') {
// delete one of message/error and retry
}
throw e;
} Prevention
- Use message (string) OR error (function), never both, in any check/refine params.
- When spreading a base params object, delete the stale key before adding the new one.
- Lint for objects containing both message and error keys.
When it happens
Trigger: Passing `{ message: 'bad', error: () => 'bad' }` to `.min(1, {...})`, `.refine(..., { message, error })`, or any schema/check that accepts params. Also from spreading a base params object that already has `error` and then adding `message`.
Common situations: Refactoring from `error:` (older API) to `message:` and leaving both; merging param objects via spread where one had `error` and the other added `message`; copy-paste between checks.
Related errors
- Invalid discriminated union option at index "${def.options.i
- Invalid discriminated union option at index "${def.options.i
- Duplicate discriminator value "${String(v)}"
- Async schemas not supported in object keys currently
- Cannot create literal schema with no valid values
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/a09b277da74796bf.json.
Report an issue: GitHub.