Basic Usage
Create a schema that requires the input to satisfy all provided schemas.
import { z } from 'zod';
const HasId = z.object({ id: z.string() });
const HasName = z.object({ name: z.string() });
const User = z.intersection(HasId, HasName);
User.parse({ id: '123', name: 'Alice' }); // ✓ Valid
User.parse({ id: '123' }); // ✗ Invalid - missing name
User.parse({ name: 'Alice' }); // ✗ Invalid - missing id
type User = z.infer<typeof User>;
// { id: string } & { name: string }
// Simplifies to: { id: string; name: string }
Signature
function intersection<T extends SomeType, U extends SomeType>(
left: T,
right: U
): ZodIntersection<T, U>
The first schema to intersect.
The second schema to intersect.
Convenience Method
Use the .and() method as shorthand:
const HasId = z.object({ id: z.string() });
const HasName = z.object({ name: z.string() });
const User = HasId.and(HasName);
// Equivalent to: z.intersection(HasId, HasName)
type User = z.infer<typeof User>;
// { id: string; name: string }
Object Intersections
Merge object shapes:
const BaseEntity = z.object({
id: z.string(),
createdAt: z.date(),
});
const NamedEntity = z.object({
name: z.string(),
description: z.string(),
});
const Product = z.intersection(BaseEntity, NamedEntity);
type Product = z.infer<typeof Product>;
// {
// id: string;
// createdAt: Date;
// name: string;
// description: string;
// }
For objects, prefer .extend() over .intersection() for better type inference and performance.
// Recommended approach
const Product = BaseEntity.extend({
name: z.string(),
description: z.string(),
});
// Also works: spread the shape
const Product = BaseEntity.extend(NamedEntity.shape);
Type Conflicts
Intersecting incompatible types creates impossible schemas:
const Impossible = z.intersection(
z.string(),
z.number()
);
type Impossible = z.infer<typeof Impossible>;
// string & number (never)
// This will always fail at runtime
Impossible.parse('hello'); // ✗ Invalid - not a number
Impossible.parse(42); // ✗ Invalid - not a string
Overlapping Object Properties
When objects have the same property:
const A = z.object({ value: z.string() });
const B = z.object({ value: z.number() });
const Conflict = z.intersection(A, B);
type Conflict = z.infer<typeof Conflict>;
// { value: string } & { value: number }
// Property 'value' is string & number (never)
// This will always fail
Conflict.parse({ value: 'test' }); // ✗ Invalid
Conflict.parse({ value: 42 }); // ✗ Invalid
To handle this, use union types:
const A = z.object({ value: z.string() });
const B = z.object({ value: z.number() });
const Compatible = z.object({
value: z.union([z.string(), z.number()]),
});
type Compatible = z.infer<typeof Compatible>;
// { value: string | number }
Array Intersections
Intersecting arrays merges element constraints:
const MinTwo = z.array(z.any()).min(2);
const MaxFive = z.array(z.any()).max(5);
const Bounded = z.intersection(MinTwo, MaxFive);
Bounded.parse([1, 2]); // ✓ Valid
Bounded.parse([1, 2, 3, 4, 5]); // ✓ Valid
Bounded.parse([1]); // ✗ Invalid - too short
Bounded.parse([1, 2, 3, 4, 5, 6]); // ✗ Invalid - too long
Incompatible element types create invalid schemas:
const StringArray = z.array(z.string());
const NumberArray = z.array(z.number());
const Invalid = z.intersection(StringArray, NumberArray);
// Elements must be both string AND number (impossible)
Primitive Intersections
Intersecting primitives with refinements:
const MinLength = z.string().min(5);
const MaxLength = z.string().max(10);
const BoundedString = z.intersection(MinLength, MaxLength);
BoundedString.parse('hello'); // ✓ Valid (5 chars)
BoundedString.parse('helloworld'); // ✓ Valid (10 chars)
BoundedString.parse('hi'); // ✗ Invalid - too short
BoundedString.parse('verylongstring'); // ✗ Invalid - too long
type BoundedString = z.infer<typeof BoundedString>;
// string
Type Inference
const schema = z.intersection(
z.object({ id: z.string() }),
z.object({ name: z.string() })
);
type Output = z.infer<typeof schema>;
// { id: string } & { name: string }
// Simplifies to: { id: string; name: string }
type Input = z.input<typeof schema>;
// Same as output for intersections without transformations
Multiple Intersections
Chain multiple .and() calls:
const A = z.object({ a: z.string() });
const B = z.object({ b: z.number() });
const C = z.object({ c: z.boolean() });
const ABC = A.and(B).and(C);
// Equivalent to: z.intersection(z.intersection(A, B), C)
type ABC = z.infer<typeof ABC>;
// { a: string; b: number; c: boolean }
Common Patterns
Mixins
const Timestamped = z.object({
createdAt: z.date(),
updatedAt: z.date(),
});
const Versioned = z.object({
version: z.number(),
});
const Entity = z.object({
id: z.string(),
name: z.string(),
});
const FullEntity = Entity.and(Timestamped).and(Versioned);
type FullEntity = z.infer<typeof FullEntity>;
// {
// id: string;
// name: string;
// createdAt: Date;
// updatedAt: Date;
// version: number;
// }
Combining Constraints
const EmailString = z.string().email();
const LongString = z.string().min(10);
const LongEmail = EmailString.and(LongString);
LongEmail.parse('[email protected]'); // ✗ Invalid - too short
LongEmail.parse('[email protected]'); // ✓ Valid
type LongEmail = z.infer<typeof LongEmail>;
// string
Partial Updates
const FullUser = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
age: z.number(),
});
const UserUpdate = FullUser.partial().and(
z.object({ id: z.string() }) // id is required
);
type UserUpdate = z.infer<typeof UserUpdate>;
// {
// id: string;
// name?: string;
// email?: string;
// age?: number;
// }
UserUpdate.parse({ id: '123' }); // ✓ Valid
UserUpdate.parse({ id: '123', name: 'Alice' }); // ✓ Valid
UserUpdate.parse({ name: 'Alice' }); // ✗ Invalid - missing id
Intersection vs Union
| Feature | Intersection (AND) | Union (OR) |
|---|
| Logic | Must satisfy all schemas | Must satisfy at least one |
| Operator | .and() | .or() |
| Type | A & B | A | B |
| Objects | Merges properties | Alternative shapes |
| Use Case | Combine requirements | Alternative types |
// Intersection: Must have both properties
const both = z.object({ a: z.string() })
.and(z.object({ b: z.number() }));
type Both = z.infer<typeof both>;
// { a: string; b: number }
// Union: Can have either shape
const either = z.object({ a: z.string() })
.or(z.object({ b: z.number() }));
type Either = z.infer<typeof either>;
// { a: string } | { b: number }
Limitations
Intersections have limitations with:
- Primitive type conflicts (e.g.,
string & number)
- Incompatible array element types
- Overlapping object properties with different types
For object merging, prefer:
.extend() for adding properties
.merge() for combining objects (deprecated, use .extend(other.shape))
- Manual object construction for complex cases