Capítulo 19 de 61
z.union() passes if any option matches (checked in order, first success wins); z.xor() passes only if exactly one option matches, failing on both zero and multiple matches.
z.union([...]): logical OR — validates against each option in array order, returns the first successful parse..options (Zod) / .def.options (Zod Mini): the array of option schemas.z.xor([...]): passes only when exactly one option matches; fails with inclusive: false and a matches array of the indices that matched, if more than one option succeeds.z.xor(): because z.object() strips unknown keys by default, two object options with overlapping-but-different shapes can both "match" the same input (each just strips what it doesn't recognize) — use .strict() on the narrower option to force true mutual exclusivity.const stringOrNumber = z.union([z.string(), z.number()]);
stringOrNumber.parse("foo"); // passes
stringOrNumber.parse(14); // passes
const payment = z.xor([
z.object({ type: z.literal("card"), cardNumber: z.string() }),
z.object({ type: z.literal("bank"), accountNumber: z.string() }),
]);
payment.parse({ type: "card", cardNumber: "1234" }); // passes — exactly one option matches
// the overlap trap
const name = z.object({ name: z.string() });
const version = name.extend({ version: z.string() });
z.xor([name, version]).parse({ name: "zod", version: "4" }); // fails — matches both
z.xor([name.strict(), version]).parse({ name: "zod", version: "4" }); // passes
z.union()'s first-match-wins behavior versus z.xor()'s exactly-one-match requirement, and the strict-object fix for accidental overlap.z.xor() with plain (non-strict) object options that share a prefix of fields: they'll silently overlap because unknown keys get stripped rather than causing a mismatch — the loose object doesn't actually reject the extra key.z.union() is "at least one matches"; z.xor() is "exactly one matches" — don't reach for xor unless mutual exclusivity is a real requirement.z.union() since it short-circuits on first success; for genuinely exclusive shapes, prefer z.discriminatedUnion() for both correctness and performance.z.xor() with object schemas, apply .strict() to prevent looser options from silently matching inputs meant for a stricter one.z.union() when all options share a literal discriminant key..strict()/z.strictObject(), the fix for z.xor() overlap.