Skip to main content
Creates a schema using a Zod schema definition with automatic type inference. This is the recommended way to define schemas in the AI SDK when using TypeScript.
import { zodSchema } from 'ai';
import { z } from 'zod';

const schema = zodSchema(
  z.object({
    name: z.string(),
    age: z.number(),
  })
);

Parameters

zodSchema
z.ZodType<OBJECT>
required
The Zod schema that defines the shape of the object.Supports both Zod v3 and Zod v4.
useReferences
boolean
default:"false"
Enables support for references in the schema. This is required for recursive schemas, e.g. with z.lazy.However, not all language models and providers support such references.

Returns

Returns a Schema<OBJECT> that can be used with AI SDK functions. The type is automatically inferred from the Zod schema.

Examples

Basic Zod schema

import { zodSchema } from 'ai';
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const personSchema = zodSchema(
  z.object({
    name: z.string().describe('The person\'s full name'),
    age: z.number().describe('The person\'s age in years'),
    occupation: z.string().optional().describe('The person\'s occupation'),
  })
);

const result = await generateObject({
  model: openai('gpt-4-turbo'),
  schema: personSchema,
  prompt: 'Generate a person profile',
});

// Type is automatically inferred:
// result.object: {
//   name: string;
//   age: number;
//   occupation?: string;
// }

With tool

import { tool } from 'ai';
import { zodSchema } from 'ai';
import { z } from 'zod';

const weatherTool = tool({
  description: 'Get the weather for a location',
  inputSchema: zodSchema(
    z.object({
      location: z.string().describe('City and state, e.g. San Francisco, CA'),
      unit: z.enum(['celsius', 'fahrenheit']).default('fahrenheit'),
    })
  ),
  execute: async ({ location, unit }) => {
    const weather = await fetchWeather(location, unit);
    return weather;
  },
});

Complex nested schema

import { zodSchema } from 'ai';
import { z } from 'zod';

const recipeSchema = zodSchema(
  z.object({
    name: z.string().describe('Recipe name'),
    ingredients: z.array(
      z.object({
        name: z.string(),
        quantity: z.string(),
        unit: z.string().optional(),
      })
    ).describe('List of ingredients'),
    steps: z.array(z.string()).describe('Cooking instructions'),
    prepTime: z.number().describe('Prep time in minutes'),
    cookTime: z.number().describe('Cook time in minutes'),
    servings: z.number().describe('Number of servings'),
    difficulty: z.enum(['easy', 'medium', 'hard']),
  })
);

With refinements and transforms

import { zodSchema } from 'ai';
import { z } from 'zod';

const emailSchema = zodSchema(
  z.object({
    email: z.string().email('Invalid email format'),
    age: z.number().min(0).max(150),
    website: z.string().url().optional(),
  })
);

Recursive schema

import { zodSchema } from 'ai';
import { z } from 'zod';

type TreeNode = {
  value: string;
  children?: TreeNode[];
};

const treeSchema: z.ZodType<TreeNode> = z.lazy(() =>
  z.object({
    value: z.string(),
    children: z.array(treeSchema).optional(),
  })
);

const schema = zodSchema(treeSchema, {
  useReferences: true, // Required for recursive schemas
});

Array schema

import { zodSchema } from 'ai';
import { z } from 'zod';

const countriesSchema = zodSchema(
  z.array(
    z.object({
      name: z.string(),
      capital: z.string(),
      population: z.number(),
      region: z.string(),
    })
  )
);

Union types

import { zodSchema } from 'ai';
import { z } from 'zod';

const notificationSchema = zodSchema(
  z.discriminatedUnion('type', [
    z.object({
      type: z.literal('email'),
      email: z.string().email(),
      subject: z.string(),
      body: z.string(),
    }),
    z.object({
      type: z.literal('sms'),
      phoneNumber: z.string(),
      message: z.string(),
    }),
    z.object({
      type: z.literal('push'),
      deviceId: z.string(),
      title: z.string(),
      body: z.string(),
    }),
  ])
);

With default values

import { zodSchema } from 'ai';
import { z } from 'zod';

const settingsSchema = zodSchema(
  z.object({
    theme: z.enum(['light', 'dark']).default('light'),
    notifications: z.boolean().default(true),
    language: z.string().default('en'),
    pageSize: z.number().min(10).max(100).default(20),
  })
);

Optional and nullable fields

import { zodSchema } from 'ai';
import { z } from 'zod';

const userSchema = zodSchema(
  z.object({
    id: z.string(),
    name: z.string(),
    email: z.string().email(),
    // Optional field (can be undefined)
    bio: z.string().optional(),
    // Nullable field (can be null)
    avatar: z.string().nullable(),
    // Can be undefined or null
    phoneNumber: z.string().optional().nullable(),
  })
);

Enum schema

import { zodSchema } from 'ai';
import { z } from 'zod';

const statusSchema = zodSchema(
  z.enum(['pending', 'in_progress', 'completed', 'failed'])
);

Zod v3 vs Zod v4

The zodSchema function automatically detects and handles both Zod v3 and Zod v4:
import { zodSchema } from 'ai';
import * as z3 from 'zod/v3';
import * as z4 from 'zod/v4';

// Works with Zod v3
const schemaV3 = zodSchema(
  z3.object({
    name: z3.string(),
  })
);

// Works with Zod v4
const schemaV4 = zodSchema(
  z4.object({
    name: z4.string(),
  })
);

Type Inference

The main advantage of zodSchema over jsonSchema is automatic type inference:
import { zodSchema } from 'ai';
import { generateObject } from 'ai';
import { z } from 'zod';

const schema = zodSchema(
  z.object({
    name: z.string(),
    age: z.number(),
    tags: z.array(z.string()),
  })
);

const result = await generateObject({
  model: openai('gpt-4-turbo'),
  schema,
  prompt: 'Generate data',
});

// TypeScript knows the exact type:
const name: string = result.object.name;
const age: number = result.object.age;
const tags: string[] = result.object.tags;

Notes

When using descriptions on schema properties (via .describe()), the descriptions are passed to the language model to help it understand what values to generate.
Recursive schemas require useReferences: true, but not all providers support JSON Schema references. Test with your specific provider before using in production.