# How to Define a Zod Array of Objects with Nested Properties

> Learn to define a Zod array of objects with nested properties using Zod schemas. Easily infer TypeScript types and validate complex data structures effectively.

- Repository: [Microsoft/TypeScript](https://github.com/microsoft/typescript)
- Tags: how-to-guide
- Published: 2026-02-16

---

**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.

```typescript
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.

```typescript
// 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.

```typescript
// 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:

```typescript
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: "alice@example.com",
    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`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/types.ts) and [`src/compiler/checker.ts`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts).

### Array Type Checking

In [`src/compiler/checker.ts`](https://github.com/microsoft/TypeScript/blob/main/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`](https://github.com/microsoft/TypeScript/blob/main/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`](https://github.com/microsoft/TypeScript/blob/main/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`](https://github.com/microsoft/TypeScript/blob/main/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**:

```typescript
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 in [`src/compiler/checker.ts`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts) and [`src/compiler/types.ts`](https://github.com/microsoft/TypeScript/blob/main/src/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`](https://github.com/microsoft/TypeScript/blob/main/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`](https://github.com/microsoft/TypeScript/blob/main/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.