Skip to main content

Generating Structured Data

While text generation is useful, many applications need structured, typed data. The AI SDK Core provides built-in support for generating schema-validated structured data through the output property on generateText and streamText.

Why Structured Data?

Structured data generation is essential for:
  • Data extraction: Extract information from unstructured text
  • Classification: Categorize content into predefined types
  • Synthetic data: Generate test data or examples
  • Form filling: Generate structured responses for forms
  • API responses: Create typed data for downstream systems

Basic Usage

Use Output.object() with a schema to generate structured data:
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai('gpt-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.',
});

// output is fully typed and validated
console.log(output.recipe.name);
console.log(output.recipe.ingredients);

Output Types

The AI SDK supports multiple output formats through the Output object:

Output.object()

Generate a structured object matching a schema:
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.object({
    schema: z.object({
      name: z.string(),
      age: z.number(),
      email: z.string().email(),
      interests: z.array(z.string()),
    }),
  }),
  prompt: 'Generate a user profile for a software engineer.',
});

// Fully typed
console.log(output.name);      // string
console.log(output.age);       // number
console.log(output.interests); // string[]

Output.array()

Generate an array of structured elements:
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.array({
    element: z.object({
      city: z.string(),
      temperature: z.number(),
      condition: z.string(),
    }),
  }),
  prompt: 'List weather for San Francisco, London, and Tokyo.',
});

// output is an array of weather objects
for (const weather of output) {
  console.log(`${weather.city}: ${weather.temperature}°F, ${weather.condition}`);
}

Streaming Arrays

When streaming arrays, use elementStream to receive complete elements as they’re generated:
import { streamText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { elementStream } = streamText({
  model: openai('gpt-5'),
  output: Output.array({
    element: z.object({
      name: z.string(),
      class: z.string(),
      description: z.string(),
    }),
  }),
  prompt: 'Generate 3 fantasy RPG characters.',
});

for await (const character of elementStream) {
  console.log('Character:', character.name);
  console.log('Class:', character.class);
  console.log('Description:', character.description);
  console.log('---');
}

Output.choice()

Choose from a fixed set of options:
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.choice({
    options: ['positive', 'negative', 'neutral'],
  }),
  prompt: 'Classify the sentiment: "This product exceeded my expectations!"',
});

console.log(output); // 'positive' | 'negative' | 'neutral'

Output.json()

Generate arbitrary JSON without schema validation:
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.json(),
  prompt: 'Generate a JSON object with city weather data for multiple cities.',
});

// output is any valid JSON
console.log(output);
// {
//   "San Francisco": { "temperature": 65, "condition": "Foggy" },
//   "New York": { "temperature": 72, "condition": "Sunny" }
// }
Output.json() only validates that the response is valid JSON. For type safety, use Output.object() or Output.array().

Output.text()

Generate plain text (this is the default when no output is specified):
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.text(),
  prompt: 'Write a haiku about programming.',
});

console.log(output); // string

Schema Definitions

The AI SDK supports multiple schema libraries:

Zod Schemas

import { z } from 'zod';
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.object({
    schema: z.object({
      title: z.string(),
      tags: z.array(z.string()),
      published: z.boolean(),
    }),
  }),
  prompt: 'Generate blog post metadata',
});

JSON Schema

import { generateText, Output, jsonSchema } from 'ai';
import { openai } from '@ai-sdk/openai';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.object({
    schema: jsonSchema({
      type: 'object',
      properties: {
        name: { type: 'string' },
        age: { type: 'number' },
      },
      required: ['name', 'age'],
    }),
  }),
  prompt: 'Generate a person profile',
});

Property Descriptions

Add descriptions to schema properties to guide the model:
import { z } from 'zod';
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.object({
    schema: z.object({
      name: z.string().describe('The name of the recipe'),
      servings: z.number().describe('Number of servings (1-12)'),
      ingredients: z.array(
        z.object({
          name: z.string(),
          amount: z.string().describe('Amount in grams or ml'),
        })
      ).describe('List of ingredients with amounts'),
      steps: z.array(z.string()).describe('Step-by-step cooking instructions'),
    }),
  }),
  prompt: 'Generate a pasta recipe for 4 people.',
});
Descriptions help:
  • Clarify ambiguous property names
  • Specify expected formats
  • Provide context for nested structures

Output Name and Description

Provide a name and description for the output to improve generation quality:
import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.object({
    name: 'Recipe',
    description: 'A complete recipe with ingredients and steps',
    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 recipe for chocolate cake.',
});

Streaming Structured Data

Stream structured data as it’s generated using streamText:
import { streamText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { partialOutputStream } = streamText({
  model: openai('gpt-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.',
});

// Stream partial objects as they're built
for await (const partialObject of partialOutputStream) {
  console.log(partialObject);
  // { recipe: { name: "Lasagna" } }
  // { recipe: { name: "Lasagna", ingredients: [{ name: "pasta" }] } }
  // ...
}
Partial outputs cannot be validated against the schema since they’re incomplete. Validation happens on the final output.

Combining with Tools

Structured output can be combined with tool calling:
import { generateText, Output, tool, stepCountIs } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai('gpt-5'),
  tools: {
    weather: tool({
      description: 'Get weather for a location',
      inputSchema: z.object({ location: z.string() }),
      execute: async ({ location }) => {
        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?',
});

console.log(output.summary);
console.log(output.recommendation);
Generating structured output counts as a step. Configure stopWhen to allow enough steps for both tool execution and output generation.

Error Handling

When generateText cannot generate valid structured data, it throws NoObjectGeneratedError:
import { generateText, Output, NoObjectGeneratedError } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

try {
  const { output } = await generateText({
    model: openai('gpt-5'),
    output: Output.object({
      schema: z.object({
        name: z.string(),
        age: z.number(),
      }),
    }),
    prompt: 'Generate a person',
  });
} catch (error) {
  if (NoObjectGeneratedError.isInstance(error)) {
    console.log('Failed to generate object');
    console.log('Cause:', error.cause);
    console.log('Text:', error.text);
    console.log('Usage:', error.usage);
  }
}
For streaming, use the onError callback:
import { streamText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const result = streamText({
  model: openai('gpt-5'),
  output: Output.object({
    schema: z.object({ name: z.string() }),
  }),
  prompt: 'Generate data',
  onError({ error }) {
    console.error('Stream error:', error);
  },
});

Examples

Data Extraction

import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.object({
    schema: z.object({
      company: z.string(),
      position: z.string(),
      location: z.string(),
      salary: z.string().optional(),
      requirements: z.array(z.string()),
    }),
  }),
  prompt: `Extract job posting details from:
  
  "We're hiring a Senior Software Engineer in San Francisco. 
  Requirements: 5+ years experience, TypeScript, React. 
  Competitive salary."`,
});

console.log(output);

Content Classification

import { generateText, Output } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { output } = await generateText({
  model: openai('gpt-5'),
  output: Output.object({
    schema: z.object({
      category: z.enum(['technology', 'business', 'health', 'entertainment']),
      confidence: z.number().min(0).max(1),
      keywords: z.array(z.string()),
    }),
  }),
  prompt: 'Classify this article: "New AI model achieves breakthrough in natural language understanding"',
});

console.log(`Category: ${output.category}`);
console.log(`Confidence: ${output.confidence}`);

Next Steps

Tool Calling

Combine structured output with tools

Prompt Engineering

Tips for better structured data generation

Settings

Configure generation parameters