How to Define a Zod Array of Objects with Nested Properties
Use z.array() to wrap a z.object() schema that contains nested z.object() properties, then infer the TypeScript type with z.infer<typeof schema>.
Zod is a TypeScript-first schema validation library that lets you define complex data structures, including arrays where each element is an object with deeply nested properties. When you define a zod array of objects, you get both runtime validation and compile-time type safety powered by the TypeScript compiler in the microsoft/TypeScript repository.
Building a Zod Array of Objects Schema
Creating a robust schema requires composing nested object definitions before wrapping them in an array validator.
Step 1: Define the Innermost Nested Objects
Start by describing the deepest level of your data structure using z.object(). This establishes the foundation that parent objects will reference.
import { z } from "zod";
// Deepest nesting level: address structure
const addressSchema = z.object({
street: z.string(),
city: z.string(),
zip: z.string().regex(/^\d{5}(-\d{4})?$/),
});
Step 2: Compose the Parent Object Schema
Reference your nested schemas as properties in the parent object. This creates the object structure that will eventually become the array element type.
// Parent object containing nested address
const userSchema = z.object({
id: z.number().int(),
name: z.string(),
email: z.string().email(),
isActive: z.boolean().optional(),
address: addressSchema, // Nested object property
roles: z.array(z.enum(["admin", "user", "guest"])),
});
Step 3: Wrap in z.array() to Create the Array Schema
Convert your object schema into a zod array of objects using z.array(). This validates that the input is an array where every element conforms to the object shape.
// Final array schema
export const usersArraySchema = z.array(userSchema);
// Infer TypeScript types
export type User = z.infer<typeof userSchema>;
export type UsersArray = z.infer<typeof usersArraySchema>;
Complete Code Example: Zod Array of Objects
Here is a runnable example demonstrating validation of a zod array of objects with nested properties:
import { z } from "zod";
const addressSchema = z.object({
street: z.string(),
city: z.string(),
zip: z.string().regex(/^\d{5}(-\d{4})?$/),
});
const userSchema = z.object({
id: z.number().int(),
name: z.string(),
email: z.string().email(),
isActive: z.boolean().optional(),
address: addressSchema,
roles: z.array(z.enum(["admin", "user", "guest"])),
});
export const usersArraySchema = z.array(userSchema);
export type User = z.infer<typeof userSchema>;
export type UsersArray = z.infer<typeof usersArraySchema>;
// Runtime validation
const rawData = [
{
id: 1,
name: "Alice",
email: "[email protected]",
address: { street: "123 Main St", city: "Seattle", zip: "98101" },
roles: ["admin", "user"]
}
];
const parsed = usersArraySchema.parse(rawData);
// parsed is typed as UsersArray
How TypeScript Validates Zod Array Schemas
The TypeScript compiler in microsoft/TypeScript provides the type-inference backbone that makes zod array of objects possible. When you call z.infer<typeof schema>, the compiler analyzes the schema structure using components defined in src/compiler/types.ts and src/compiler/checker.ts.
Array Type Checking
In src/compiler/checker.ts, the compiler treats the z.array() wrapper as an ArrayTypeNode. The arrayIsHomogeneous and arrayIsEqualTo utilities (referenced in src/compiler/utilities.ts) verify that every element in the array conforms to the object type defined in the schema.
Object Property Validation
Each nested z.object() becomes an ObjectType in the compiler's type system. The checker walks property signatures using logic similar to getPropertyOfType in src/compiler/checker.ts, ensuring that nested properties like address.street are correctly typed as strings.
Compilation Pipeline
When you run tsc on files containing Zod schemas, src/compiler/program.ts orchestrates the compilation pipeline, driving the emit and diagnostics that catch type mismatches before runtime.
Advanced Nesting Patterns
For complex domains, you can chain multiple levels of nesting within a zod array of objects:
import { z } from "zod";
const productSchema = z.object({
sku: z.string(),
price: z.number().nonnegative(),
metadata: z.object({
tags: z.array(z.string()),
dimensions: z.object({
width: z.number(),
height: z.number(),
depth: z.number(),
})
})
});
const orderSchema = z.object({
orderId: z.string(),
customer: userSchema, // Reusing previous userSchema
products: z.array(productSchema),
placedAt: z.string().datetime(),
});
export const ordersArraySchema = z.array(orderSchema);
export type Order = z.infer<typeof orderSchema>;
Summary
- Compose nested objects first using
z.object()before wrapping them in arrays. - Use
z.array()to create a schema that validates an array where every element matches your object shape. - Infer types with
z.infer<typeof schema>to get automatic TypeScript types backed by the compiler logic insrc/compiler/checker.tsandsrc/compiler/types.ts. - Reference existing schemas to build complex nested structures without duplication.
Frequently Asked Questions
How do I make a zod array of objects optional?
Use .optional() on the array schema itself: z.array(userSchema).optional(). This allows the property to be undefined while still validating as an array when present.
Can I validate that a zod array of objects has a minimum or maximum length?
Yes. Chain .min(), .max(), or .length() onto the array schema: z.array(itemSchema).min(1).max(100). These methods enforce runtime constraints while preserving the TypeScript array type inference.
How does TypeScript infer the type of a zod array of objects?
The TypeScript compiler analyzes the schema structure through the type checker in src/compiler/checker.ts. When you use z.infer<typeof schema>, the compiler resolves the generic type parameter by evaluating the ArrayType and nested ObjectType structures defined in src/compiler/types.ts, producing a fully typed array interface.
What is the difference between z.array() and z.tuple() for object collections?
Use z.array() when you need a variable-length list where every element conforms to the same object schema. Use z.tuple() when you need a fixed-length array where each position has a specific, potentially different schema. For most zod array of objects use cases, z.array() is the correct choice.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →