or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-schemas.mdcoercion.mdcollections.mderrors.mdindex.mdiso-datetime.mdjson-schema.mdlocales.mdnumber-formats.mdparsing.mdprimitives.mdrefinements.mdstring-formats.mdtransformations.mdunions-intersections.mdutilities.mdwrappers.md

unions-intersections.mddocs/

0

# Union and Intersection Types

1

2

Combine schemas using union (OR) or intersection (AND) logic.

3

4

## Union

5

6

```typescript { .api }

7

function union<T extends readonly [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]>(schemas: T): ZodUnion<T>;

8

```

9

10

**Examples:**

11

```typescript

12

// Basic union

13

z.union([z.string(), z.number()])

14

// type: string | number

15

16

// Multiple types

17

z.union([z.string(), z.number(), z.boolean()])

18

19

// Complex unions

20

z.union([

21

z.object({ type: z.literal("user"), name: z.string() }),

22

z.object({ type: z.literal("admin"), permissions: z.array(z.string()) }),

23

])

24

25

// Nullable/optional patterns

26

z.union([z.string(), z.null()]) // string | null

27

z.union([z.string(), z.undefined()]) // string | undefined

28

// Better: use .nullable() or .optional()

29

```

30

31

## Discriminated Union

32

33

Optimized union validation using a discriminator field.

34

35

```typescript { .api }

36

function discriminatedUnion<

37

Discriminator extends string,

38

Options extends ZodDiscriminatedUnionOption<Discriminator>[]

39

>(discriminator: Discriminator, options: Options): ZodDiscriminatedUnion<Discriminator, Options>;

40

```

41

42

**Examples:**

43

```typescript

44

// Basic discriminated union

45

const MessageSchema = z.discriminatedUnion("type", [

46

z.object({ type: z.literal("text"), content: z.string() }),

47

z.object({ type: z.literal("image"), url: z.string().url() }),

48

z.object({ type: z.literal("video"), url: z.string().url(), duration: z.number() }),

49

]);

50

51

type Message = z.infer<typeof MessageSchema>;

52

// { type: "text"; content: string }

53

// | { type: "image"; url: string }

54

// | { type: "video"; url: string; duration: number }

55

56

// API response types

57

const ResponseSchema = z.discriminatedUnion("status", [

58

z.object({ status: z.literal("success"), data: z.any() }),

59

z.object({ status: z.literal("error"), error: z.string() }),

60

z.object({ status: z.literal("loading") }),

61

]);

62

63

// Form state

64

const FormStateSchema = z.discriminatedUnion("state", [

65

z.object({ state: z.literal("idle") }),

66

z.object({ state: z.literal("submitting") }),

67

z.object({ state: z.literal("success"), data: z.any() }),

68

z.object({ state: z.literal("error"), error: z.string() }),

69

]);

70

```

71

72

## Intersection

73

74

Combines two schemas (AND logic).

75

76

```typescript { .api }

77

function intersection<A extends ZodTypeAny, B extends ZodTypeAny>(

78

left: A,

79

right: B

80

): ZodIntersection<A, B>;

81

82

// Or use method

83

schemaA.and(schemaB);

84

```

85

86

**Examples:**

87

```typescript

88

// Basic intersection

89

const BaseUser = z.object({ id: z.string(), name: z.string() });

90

const WithEmail = z.object({ email: z.string().email() });

91

92

z.intersection(BaseUser, WithEmail)

93

// Or: BaseUser.and(WithEmail)

94

// type: { id: string; name: string; email: string }

95

96

// Multiple intersections

97

const WithTimestamps = z.object({

98

createdAt: z.date(),

99

updatedAt: z.date(),

100

});

101

102

BaseUser.and(WithEmail).and(WithTimestamps)

103

104

// Note: For objects, .merge() is usually better

105

BaseUser.merge(WithEmail).merge(WithTimestamps)

106

```

107

108

## Common Patterns

109

110

```typescript

111

// Nullable pattern

112

const NullableString = z.union([z.string(), z.null()]);

113

// Better: z.string().nullable()

114

115

// Optional pattern

116

const OptionalString = z.union([z.string(), z.undefined()]);

117

// Better: z.string().optional()

118

119

// API response with success/error

120

const APIResponse = z.discriminatedUnion("success", [

121

z.object({

122

success: z.literal(true),

123

data: z.any(),

124

}),

125

z.object({

126

success: z.literal(false),

127

error: z.string(),

128

code: z.number(),

129

}),

130

]);

131

132

// Event types

133

const EventSchema = z.discriminatedUnion("event", [

134

z.object({ event: z.literal("click"), x: z.number(), y: z.number() }),

135

z.object({ event: z.literal("scroll"), scrollY: z.number() }),

136

z.object({ event: z.literal("resize"), width: z.number(), height: z.number() }),

137

]);

138

139

// Payment methods

140

const PaymentSchema = z.discriminatedUnion("method", [

141

z.object({

142

method: z.literal("card"),

143

cardNumber: z.string(),

144

cvv: z.string(),

145

}),

146

z.object({

147

method: z.literal("paypal"),

148

email: z.string().email(),

149

}),

150

z.object({

151

method: z.literal("crypto"),

152

wallet: z.string(),

153

currency: z.enum(["BTC", "ETH"]),

154

}),

155

]);

156

157

// Composing with intersection

158

const BaseEntity = z.object({ id: z.string() });

159

const Timestamped = z.object({ createdAt: z.date(), updatedAt: z.date() });

160

const Audited = z.object({ createdBy: z.string(), updatedBy: z.string() });

161

162

const FullEntity = BaseEntity.and(Timestamped).and(Audited);

163

// Or use merge for objects

164

const FullEntityMerged = BaseEntity.merge(Timestamped).merge(Audited);

165

```

166

167

## Type Inference

168

169

```typescript

170

const UnionSchema = z.union([z.string(), z.number()]);

171

type UnionType = z.infer<typeof UnionSchema>; // string | number

172

173

const DiscriminatedSchema = z.discriminatedUnion("type", [

174

z.object({ type: z.literal("a"), value: z.string() }),

175

z.object({ type: z.literal("b"), value: z.number() }),

176

]);

177

type DiscriminatedType = z.infer<typeof DiscriminatedSchema>;

178

// { type: "a"; value: string } | { type: "b"; value: number }

179

180

const IntersectionSchema = z.intersection(

181

z.object({ a: z.string() }),

182

z.object({ b: z.number() })

183

);

184

type IntersectionType = z.infer<typeof IntersectionSchema>;

185

// { a: string; b: number }

186

```

187

188

## When to Use

189

190

- **Union**: Multiple valid types (OR logic)

191

- **Discriminated Union**: Tagged union types with better performance

192

- **Intersection**: Combine types (AND logic), but prefer `.merge()` for objects

193