Generating Structured Data

While text generation can be useful, your use case will likely call for generating structured data. For example, you might want to extract information from text, classify data, or generate synthetic data.

Many language models are capable of generating structured data, often defined as using "JSON modes" or "tools". However, you need to manually provide schemas and then validate the generated data as LLMs can produce incorrect or incomplete structured data.

The AI SDK standardises structured object generation across model providers using the output property on generateText and streamText. You can use Zod schemas, Valibot, or JSON schemas to specify the shape of the data that you want, and the AI model will generate data that conforms to that structure.

Structured output generation is part of the generateText and streamText flow. This means you can combine it with tool calling in the same request.

Generating Structured Outputs

Use generateText with Output.object() to generate structured data from a prompt. The schema is also used to validate the generated data, ensuring type safety and correctness.

import { generateText, Output } from 'ai';
import { z } from 'zod';
const { output } = await generateText({
model: "anthropic/claude-sonnet-4.5",
output: Output.object({
schema: z.object({
recipe: z.object({
name: z.string(),
ingredients: z.array(
z.object({ name: z.string(), amount: z.string() }),
),
steps: z.array(z.string()),
}),
}),
}),
prompt: 'Generate a lasagna recipe.',
});

Structured output generation counts as a step in the AI SDK's multi-turn execution model (where each model call or tool execution is one step). When combining with tools, account for this in your stopWhen configuration.

Accessing response headers & body

Sometimes you need access to the full response from the model provider, e.g. to access some provider-specific headers or body content.

You can access the raw response headers and body using the response property:

import { generateText, Output } from 'ai';
const result = await generateText({
// ...
output: Output.object({ schema }),
});
console.log(JSON.stringify(result.response.headers, null, 2));
console.log(JSON.stringify(result.response.body, null, 2));

Stream Structured Outputs

Given the added complexity of returning structured data, model response time can be unacceptable for your interactive use case. With streamText and output, you can stream the model's structured response as it is generated.

import { streamText, Output } from 'ai';
import { z } from 'zod';
const { partialOutputStream } = streamText({
model: "anthropic/claude-sonnet-4.5",
output: Output.object({
schema: z.object({
recipe: z.object({
name: z.string(),
ingredients: z.array(
z.object({ name: z.string(), amount: z.string() }),
),
steps: z.array(z.string()),
}),
}),
}),
prompt: 'Generate a lasagna recipe.',
});
// use partialOutputStream as an async iterable
for await (const partialObject of partialOutputStream) {
console.log(partialObject);
}

You can consume the structured output on the client with the useObject hook.

Error Handling in Streams

streamText starts streaming immediately. When errors occur during streaming, they become part of the stream rather than thrown exceptions (to prevent stream crashes).

To handle errors, provide an onError callback:

import { streamText, Output } from 'ai';
const result = streamText({
// ...
output: Output.object({ schema }),
onError({ error }) {
console.error(error); // log to your error tracking service
},
});

For non-streaming error handling with generateText, see the Error Handling section below.

Output Types

The AI SDK supports multiple ways of specifying the expected structure of generated data via the Output object. You can select from various strategies for structured/text generation and validation.

Output.text()

Use Output.text() to generate plain text from a model. This option doesn't enforce any schema on the result: you simply receive the model's text as a string. This is the default behavior when no output is specified.

import { generateText, Output } from 'ai';
const { output } = await generateText({
// ...
output: Output.text(),
prompt: 'Tell me a joke.',
});
// output will be a string (the joke)

Output.object()

Use Output.object({ schema }) to generate a structured object based on a schema (for example, a Zod schema). The output is type-validated to ensure the returned result matches the schema.

import { generateText, Output } from 'ai';
import { z } from 'zod';
const { output } = await generateText({
// ...
output: Output.object({
schema: z.object({
name: z.string(),
age: z.number().nullable(),
labels: z.array(z.string()),
}),
}),
prompt: 'Generate information for a test user.',
});
// output will be an object matching the schema above

Partial outputs streamed via streamText cannot be validated against your provided schema, as incomplete data may not yet conform to the expected structure.

Output.array()

Use Output.array({ element }) to specify that you expect an array of typed objects from the model, where each element should conform to a schema (defined in the element property).

import { generateText, Output } from 'ai';
import { z } from 'zod';
const { output } = await generateText({
// ...
output: Output.array({
element: z.object({
location: z.string(),
temperature: z.number(),
condition: z.string(),
}),
}),
prompt: 'List the weather for San Francisco and Paris.',
});
// output will be an array of objects like:
// [
// { location: 'San Francisco', temperature: 70, condition: 'Sunny' },
// { location: 'Paris', temperature: 65, condition: 'Cloudy' },
// ]

With streamText, you can also iterate over the generated array elements using elementStream:

import { streamText, Output } from 'ai';
import { z } from 'zod';
const { elementStream } = streamText({
model: "anthropic/claude-sonnet-4.5",
output: Output.array({
element: z.object({
name: z.string(),
class: z
.string()
.describe('Character class, e.g. warrior, mage, or thief.'),
description: z.string(),
}),
}),
prompt: 'Generate 3 hero descriptions for a fantasy role playing game.',
});
for await (const hero of elementStream) {
console.log(hero);
}

Each element emitted by elementStream is complete and validated against your element schema, ensuring type safety for each item as it is generated.

Output.choice()

Use Output.choice({ options }) when you expect the model to choose from a specific set of string options, such as for classification or fixed-enum answers.

import { generateText, Output } from 'ai';
const { output } = await generateText({
// ...
output: Output.choice({
options: ['sunny', 'rainy', 'snowy'],
}),
prompt: 'Is the weather sunny, rainy, or snowy today?',
});
// output will be one of: 'sunny', 'rainy', or 'snowy'

You can provide any set of string options, and the output will always be a single string value that matches one of the specified options. The AI SDK validates that the result matches one of your options, and will throw if the model returns something invalid.

This is especially useful for making classification-style generations or forcing valid values for API compatibility.

Output.json()

Use Output.json() when you want to generate and parse unstructured JSON values from the model, without enforcing a specific schema. This is useful if you want to capture arbitrary objects, flexible structures, or when you want to rely on the model's natural output rather than rigid validation.

import { generateText, Output } from 'ai';
const { output } = await generateText({
// ...
output: Output.json(),
prompt:
'For each city, return the current temperature and weather condition as a JSON object.',
});
// output could be any valid JSON, for example:
// {
// "San Francisco": { "temperature": 70, "condition": "Sunny" },
// "Paris": { "temperature": 65, "condition": "Cloudy" }
// }

With Output.json, the AI SDK only checks that the response is valid JSON; it doesn't validate the structure or types of the values. If you need schema validation, use the .object or .array outputs instead.

For more advanced validation or different structures, see the Output API reference.

Generating Structured Outputs with Tools

One of the key advantages of using structured output with generateText and streamText is the ability to combine it with tool calling.

import { generateText, Output, tool, stepCountIs } from 'ai';
import { z } from 'zod';
const { output } = await generateText({
model: "anthropic/claude-sonnet-4.5",
tools: {
weather: tool({
description: 'Get the weather for a location',
inputSchema: z.object({ location: z.string() }),
execute: async ({ location }) => {
// fetch weather data
return { temperature: 72, condition: 'sunny' };
},
}),
},
output: Output.object({
schema: z.object({
summary: z.string(),
recommendation: z.string(),
}),
}),
stopWhen: stepCountIs(5),
prompt: 'What should I wear in San Francisco today?',
});

When using tools with structured output, remember that generating the structured output counts as a step. Configure stopWhen to allow enough steps for both tool execution and output generation.

Property Descriptions

You can add .describe("...") to individual schema properties to give the model hints about what each property is for. This helps improve the quality and accuracy of generated structured data:

import { generateText, Output } from 'ai';
import { z } from 'zod';
const { output } = await generateText({
model: "anthropic/claude-sonnet-4.5",
output: Output.object({
schema: z.object({
name: z.string().describe('The name of the recipe'),
ingredients: z
.array(
z.object({
name: z.string(),
amount: z
.string()
.describe('The amount of the ingredient (grams or ml)'),
}),
)
.describe('List of ingredients with amounts'),
steps: z.array(z.string()).describe('Step-by-step cooking instructions'),
}),
}),
prompt: 'Generate a lasagna recipe.',
});

Property descriptions are particularly useful for:

  • Clarifying ambiguous property names
  • Specifying expected formats or conventions
  • Providing context for complex nested structures

Accessing Reasoning

You can access the reasoning used by the language model to generate the object via the reasoning property on the result. This property contains a string with the model's thought process, if available.

import { generateText, Output } from 'ai';
import { z } from 'zod';
const result = await generateText({
model: "anthropic/claude-sonnet-4.5", // must be a reasoning model
output: Output.object({
schema: z.object({
recipe: z.object({
name: z.string(),
ingredients: z.array(
z.object({
name: z.string(),
amount: z.string(),
}),
),
steps: z.array(z.string()),
}),
}),
}),
prompt: 'Generate a lasagna recipe.',
});
console.log(result.reasoning);

Error Handling

When generateText with structured output cannot generate a valid object, it throws a AI_NoObjectGeneratedError.

This error occurs when the AI provider fails to generate a parsable object that conforms to the schema. It can arise due to the following reasons:

  • The model failed to generate a response.
  • The model generated a response that could not be parsed.
  • The model generated a response that could not be validated against the schema.

The error preserves the following information to help you log the issue:

  • text: The text that was generated by the model. This can be the raw text or the tool call text, depending on the object generation mode.
  • response: Metadata about the language model response, including response id, timestamp, and model.
  • usage: Request token usage.
  • cause: The cause of the error (e.g. a JSON parsing error). You can use this for more detailed error handling.
import { generateText, Output, NoObjectGeneratedError } from 'ai';
try {
await generateText({
model,
output: Output.object({ schema }),
prompt,
});
} catch (error) {
if (NoObjectGeneratedError.isInstance(error)) {
console.log('NoObjectGeneratedError');
console.log('Cause:', error.cause);
console.log('Text:', error.text);
console.log('Response:', error.response);
console.log('Usage:', error.usage);
}
}

generateObject and streamObject (Legacy)

generateObject and streamObject are deprecated. Use generateText and streamText with the output property instead. The legacy functions will be removed in a future major version.

The generateObject and streamObject functions are the legacy way to generate structured data. They work similarly to generateText and streamText with Output.object(), but as standalone functions.

generateObject

import { generateObject } from 'ai';
import { z } from 'zod';
const { object } = await generateObject({
model: "anthropic/claude-sonnet-4.5",
schema: z.object({
recipe: z.object({
name: z.string(),
ingredients: z.array(z.object({ name: z.string(), amount: z.string() })),
steps: z.array(z.string()),
}),
}),
prompt: 'Generate a lasagna recipe.',
});

streamObject

import { streamObject } from 'ai';
import { z } from 'zod';
const { partialObjectStream } = streamObject({
model: "anthropic/claude-sonnet-4.5",
schema: z.object({
recipe: z.object({
name: z.string(),
ingredients: z.array(z.object({ name: z.string(), amount: z.string() })),
steps: z.array(z.string()),
}),
}),
prompt: 'Generate a lasagna recipe.',
});
for await (const partialObject of partialObjectStream) {
console.log(partialObject);
}

Schema Name and Description (Legacy)

You can optionally specify a name and description for the schema. These are used by some providers for additional LLM guidance, e.g. via tool or schema name.

import { generateObject } from 'ai';
import { z } from 'zod';
const { object } = await generateObject({
model: "anthropic/claude-sonnet-4.5",
schemaName: 'Recipe',
schemaDescription: 'A recipe for a dish.',
schema: z.object({
name: z.string(),
ingredients: z.array(z.object({ name: z.string(), amount: z.string() })),
steps: z.array(z.string()),
}),
prompt: 'Generate a lasagna recipe.',
});

Output Strategy (Legacy)

The legacy functions support different output strategies via the output parameter:

Array

Generate an array of objects. The schema specifies the shape of an array element.

import { streamObject } from 'ai';
import { z } from 'zod';
const { elementStream } = streamObject({
model: "anthropic/claude-sonnet-4.5",
output: 'array',
schema: z.object({
name: z.string(),
class: z
.string()
.describe('Character class, e.g. warrior, mage, or thief.'),
description: z.string(),
}),
prompt: 'Generate 3 hero descriptions for a fantasy role playing game.',
});
for await (const hero of elementStream) {
console.log(hero);
}

Enum

Generate a specific enum value for classification tasks.

import { generateObject } from 'ai';
const { object } = await generateObject({
model: "anthropic/claude-sonnet-4.5",
output: 'enum',
enum: ['action', 'comedy', 'drama', 'horror', 'sci-fi'],
prompt:
'Classify the genre of this movie plot: ' +
'"A group of astronauts travel through a wormhole in search of a ' +
'new habitable planet for humanity."',
});

No Schema

Generate unstructured JSON without a schema.

import { generateObject } from 'ai';
const { object } = await generateObject({
model: "anthropic/claude-sonnet-4.5",
output: 'no-schema',
prompt: 'Generate a lasagna recipe.',
});

Repairing Invalid JSON (Legacy)

The repairText function is experimental and may change in the future.

Sometimes the model will generate invalid or malformed JSON. You can use the repairText function to attempt to repair the JSON.

import { generateObject } from 'ai';
const { object } = await generateObject({
model,
schema,
prompt,
experimental_repairText: async ({ text, error }) => {
// example: add a closing brace to the text
return text + '}';
},
});

More Examples

You can see generateObject and streamObject in action using various frameworks in the following examples:

generateObject

Learn to generate objects in Node.js
Learn to generate objects in Next.js with Route Handlers (AI SDK UI)
Learn to generate objects in Next.js with Server Actions (AI SDK RSC)

streamText with Output

Learn to stream objects in Node.js
Learn to stream objects in Next.js with Route Handlers (AI SDK UI)
Learn to stream objects in Next.js with Server Actions (AI SDK RSC)