You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: packages/docs/content/api.mdx
+55Lines changed: 55 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1700,6 +1700,38 @@ console.log(optionalUrl.safeParse("not a valid url").success); // false
1700
1700
1701
1701
<br/> */}
1702
1702
1703
+
## Exclusive unions (XOR)
1704
+
1705
+
An exclusive union (XOR) is a union where exactly one option must match. Unlike regular unions that succeed when any option matches, `z.xor()` fails if zero options match OR if multiple options match.
1706
+
1707
+
```ts
1708
+
const schema =z.xor([z.string(), z.number()]);
1709
+
1710
+
schema.parse("hello"); // ✅ passes
1711
+
schema.parse(42); // ✅ passes
1712
+
schema.parse(true); // ❌ fails (zero matches)
1713
+
```
1714
+
1715
+
This is useful when you want to ensure mutual exclusivity between options:
If the input could match multiple options, `z.xor()` will fail:
1728
+
1729
+
```ts
1730
+
const overlapping =z.xor([z.string(), z.any()]);
1731
+
overlapping.parse("hello"); // ❌ fails (matches both string and any)
1732
+
```
1733
+
1734
+
1703
1735
## Discriminated unions
1704
1736
1705
1737
A [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions) is a special kind of union in which a) all the options are object schemas that b) share a particular key (the "discriminator"). Based on the value of the discriminator key, TypeScript is able to "narrow" the type signature as you'd expect.
@@ -1768,6 +1800,7 @@ Each option should be an *object schema* whose discriminator prop (`status` in t
1768
1800
</Accordion>
1769
1801
</Accordions>
1770
1802
1803
+
1771
1804
## Intersections
1772
1805
1773
1806
Intersection types (`A & B`) represent a logical "AND".
@@ -1802,6 +1835,8 @@ type EmployedPerson = z.infer<typeof EmployedPerson>;
1802
1835
1803
1836
Record schemas are used to validate types such as `Record<string, string>`.
@@ -1828,6 +1863,9 @@ const Person = z.record(Keys, z.string());
1828
1863
// { id: string; name: string; email: string }
1829
1864
```
1830
1865
1866
+
### `z.partialRecord`
1867
+
1868
+
1831
1869
<Callout>
1832
1870
**Zod 4** — In Zod 4, if you pass a `z.enum` as the first argument to `z.record()`, Zod will exhaustively check that all enum values exist in the input as keys. This behavior agrees with TypeScript:
1833
1871
@@ -1849,6 +1887,23 @@ const Person = z.partialRecord(Keys, z.string());
1849
1887
// { id?: string; name?: string; email?: string }
1850
1888
```
1851
1889
1890
+
### `z.looseRecord`
1891
+
1892
+
By default, `z.record()` errors on keys that don't match the key schema. Use `z.looseRecord()` to pass through non-matching keys unchanged. This is particularly useful when combined with intersections to model multiple pattern properties:
0 commit comments