colinhacks/zod · error · Error

Discriminator property ${String(discriminator)} has duplicat

Error message

Discriminator property ${String(discriminator)} has duplicate value ${String(value)}

What it means

Thrown by z.discriminatedUnion when two or more options resolve to the same discriminator value. The dispatch map keys must be unique or the union cannot pick a branch deterministically.

Source

Thrown at packages/zod/src/v3/types.ts:3206

  >(
    discriminator: Discriminator,
    options: Types,
    params?: RawCreateParams
  ): ZodDiscriminatedUnion<Discriminator, Types> {
    // Get all the valid discriminator values
    const optionsMap: Map<Primitive, Types[number]> = new Map();

    // try {
    for (const type of options) {
      const discriminatorValues = getDiscriminator(type.shape[discriminator]);
      if (!discriminatorValues.length) {
        throw new Error(
          `A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`
        );
      }
      for (const value of discriminatorValues) {
        if (optionsMap.has(value)) {
          throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`);
        }

        optionsMap.set(value, type);
      }
    }

    return new ZodDiscriminatedUnion<
      Discriminator,
      // DiscriminatorValue,
      Types
    >({
      typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion,
      discriminator,
      options,
      optionsMap,
      ...processCreateParams(params),
    });
  }

View on GitHub (pinned to 912f0f51b0)

Solutions

  1. Make every option's discriminator value(s) mutually exclusive.
  2. If the same payload should validate against multiple shapes, model it as z.intersection or merge the schemas instead of a discriminated union.
  3. List the discriminator values per branch and dedupe.

Example fix

// before
z.discriminatedUnion('type', [
  z.object({ type: z.literal('x'), a: z.string() }),
  z.object({ type: z.literal('x'), b: z.number() }), // duplicate 'x'
]);

// after
z.discriminatedUnion('type', [
  z.object({ type: z.literal('x'), a: z.string() }),
  z.object({ type: z.literal('y'), b: z.number() }),
]);
Defensive patterns

Strategy: validation

Validate before calling

function assertDistinctDiscriminators(disc: string, options: z.ZodObject<any>[]) {
  const seen = new Set<unknown>();
  for (const o of options) {
    const f = o.shape[disc];
    const values = f instanceof z.ZodLiteral ? [f.value]
      : f instanceof z.ZodEnum ? f.options : [];
    for (const v of values) {
      if (seen.has(v)) throw new Error(`Duplicate discriminator: ${String(v)}`);
      seen.add(v);
    }
  }
}

Prevention

When it happens

Trigger: Two options whose discriminator field uses the same z.literal, or whose literals/enums collapse to the same primitive, e.g. `z.object({ type: z.literal('x') })` appearing twice, or `z.enum(['x','y'])` overlapping another option's `z.literal('x')`.

Common situations: Copy-pasting a branch and forgetting to change the tag; merging schemas where tags collide; an option that uses an enum sharing a value with another branch's literal.

Related errors


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