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

  1. Use only one: `message` (string) OR `error` (function), never both.
  2. 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';`.
  3. 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

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


AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03). Data as JSON: /data/errors/a09b277da74796bf.json. Report an issue: GitHub.