{"record":{"id":"b7218630a8958ab0","repo":"colinhacks/zod","slug":"invalid-discriminated-union-option-at-index-def","errorCode":null,"errorMessage":"Invalid discriminated union option at index \"${def.options.indexOf(option)}\"","messagePattern":"Invalid discriminated union option at index \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/zod/src/v4/core/schemas.ts","lineNumber":2390,"sourceCode":"  Disc extends string = string,\n> extends $ZodType {\n  _zod: $ZodDiscriminatedUnionInternals<Options, Disc>;\n}\n\nexport const $ZodDiscriminatedUnion: core.$constructor<$ZodDiscriminatedUnion> =\n  /*@__PURE__*/\n  core.$constructor(\"$ZodDiscriminatedUnion\", (inst, def) => {\n    def.inclusive = false;\n\n    $ZodUnion.init(inst, def);\n\n    const _super = inst._zod.parse;\n    util.defineLazy(inst._zod, \"propValues\", () => {\n      const propValues: util.PropValues = {};\n      for (const option of def.options) {\n        const pv = option._zod.propValues;\n        if (!pv || Object.keys(pv).length === 0)\n          throw new Error(`Invalid discriminated union option at index \"${def.options.indexOf(option)}\"`);\n        for (const [k, v] of Object.entries(pv!)) {\n          if (!propValues[k]) propValues[k] = new Set();\n          for (const val of v) {\n            propValues[k].add(val);\n          }\n        }\n      }\n      return propValues;\n    });\n\n    const disc = util.cached(() => {\n      const opts = def.options as $ZodTypeDiscriminable[];\n      const map: Map<util.Primitive, $ZodType> = new Map();\n      for (const o of opts) {\n        const values = o._zod.propValues?.[def.discriminator];\n        if (!values || values.size === 0)\n          throw new Error(`Invalid discriminated union option at index \"${def.options.indexOf(o)}\"`);\n        for (const v of values) {","sourceCodeStart":2372,"sourceCodeEnd":2408,"githubUrl":"https://github.com/colinhacks/zod/blob/2d90846af918af9602e088812d63a035d47cdbe4/packages/zod/src/v4/core/schemas.ts#L2372-L2408","documentation":"Thrown inside the lazy propValues initializer of a discriminated union when iterating def.options and an option's _zod.propValues is empty or undefined. propValues are the literal/enum values a schema can produce (used for discriminator detection); an option that contributes none — e.g. a plain z.any(), z.unknown(), z.object({}) with no literal fields, or a schema type that never publishes propValues — cannot participate in discrimination and is rejected when propValues is first accessed.","triggerScenarios":"Calling z.discriminatedUnion(\"type\", [SchemaA, z.object({})]) where one option has no discriminable literal fields; including z.any(), z.unknown(), z.never(), a non-literal z.string(), or a passthrough object as an option; wrapping the discriminator field in a transform that strips propValues.","commonSituations":"Mistakenly mixing a generic catch-all option into a discriminated union; refactoring an option to remove the literal discriminator field; using z.literal-less enums where the discriminator key was renamed in one branch; upgrading from z.union() (which has no discriminator requirement) to z.discriminatedUnion() without ensuring every option has a literal at the discriminator key.","solutions":["Ensure every option is an object schema containing a literal (or enum/literals) at the discriminator key.","Replace any z.any()/z.unknown()/empty-object option with a concrete schema that has the discriminator field.","If you genuinely need a non-discriminable option, switch from z.discriminatedUnion() to z.union().","Inspect each option's _zod.propValues in a scratch script to find which one is empty."],"exampleFix":"// before\nconst U = z.discriminatedUnion(\"type\", [\n  z.object({ type: z.literal(\"a\"), x: z.number() }),\n  z.object({ y: z.string() }), // missing discriminator\n]);\n// after\nconst U = z.discriminatedUnion(\"type\", [\n  z.object({ type: z.literal(\"a\"), x: z.number() }),\n  z.object({ type: z.literal(\"b\"), y: z.string() }),\n]);","handlingStrategy":"validation","validationCode":"function buildDiscriminatedUnion(disc: string, options: z.ZodObject[]) {\n  for (const o of options) {\n    const pv = (o as any)._zod.propValues;\n    if (!pv || Object.keys(pv).length === 0) {\n      throw new Error(`Option is not discriminable (no propValues); use a concrete object schema.`);\n    }\n  }\n  return z.discriminatedUnion(disc, options);\n}","typeGuard":"function hasPropValues(schema: unknown): boolean {\n  const pv = (schema as any)?._zod?.propValues;\n  return !!pv && Object.keys(pv).length > 0;\n}","tryCatchPattern":null,"preventionTips":["Every discriminated-union option must be an object schema with at least one literal field.","Never include z.any()/z.unknown()/z.never() as an option.","If you need a catch-all option, use z.union() instead of z.discriminatedUnion()."],"tags":["discriminated-union","schema-construction","discriminator","propvalues"],"backgroundTag":null,"analyzedSha":"2d90846af918af9602e088812d63a035d47cdbe4","analyzedAt":"2026-08-11T01:21:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}