Structured Outputs in AI Explained (2026)
Structured outputs force AI models to respond in a specific format — valid JSON matching a schema you define. Instead of hoping the model returns parseable data, you guarantee it.
The Problem
Prompt: "Extract the product name, price, and rating from this review."
Unstructured output (unpredictable):
"The product is the Sony WH-1000XM5, priced at $348, with a 4.7/5 rating."
Sometimes:
"Product: Sony WH-1000XM5\nPrice: $348\nRating: 4.7 out of 5"
Sometimes:
{"product": "Sony WH-1000XM5", "price": "$348", "rating": "4.7/5"}
Sometimes:
{"name": "Sony WH-1000XM5", "cost": 348, "score": 4.7}
Every response is different. Key names change. Formats vary. Parsing breaks.
The Solution
Structured output (guaranteed):
{
"product_name": "Sony WH-1000XM5",
"price": 348.00,
"rating": 4.7
}
Same schema. Every time. Parseable by your code. No regex. No string matching. No "please respond in JSON format" prayers.
How It Works
Define a Schema
You provide a JSON schema or Zod schema describing exactly what you want:
const schema = z.object({
product_name: z.string(),
price: z.number(),
rating: z.number().min(0).max(5),
pros: z.array(z.string()),
cons: z.array(z.string()),
});
Model Generates Conforming Output
The model's token generation is constrained to only produce valid JSON matching your schema. It's not "asking nicely" — the model literally cannot output invalid JSON.
How (technically): During token generation, the model's logits are masked so only tokens that would continue valid JSON according to the schema are allowed. Invalid next-tokens get probability zero.
You Get Typed Data
const result = await generateObject({
model: anthropic("claude-sonnet-4-20250514"),
schema,
prompt: "Extract product info from this review: ...",
});
// result.object is typed:
// { product_name: string, price: number, rating: number, pros: string[], cons: string[] }
No parsing. No try-catch around JSON.parse(). No handling malformed responses.
Provider Support
| Provider | Feature | Method |
|---|---|---|
| OpenAI | Structured Outputs | response_format: { type: "json_schema", json_schema: {...} } |
| Anthropic | Tool use for structured output | Define a tool with the desired schema |
| JSON mode | response_mime_type: "application/json" | |
| Vercel AI SDK | generateObject() | Universal abstraction across providers |
Vercel AI SDK (Recommended)
The easiest way to use structured outputs across any provider:
import { generateObject } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
const { object } = await generateObject({
model: anthropic("claude-sonnet-4-20250514"),
schema: z.object({
sentiment: z.enum(["positive", "negative", "neutral"]),
confidence: z.number().min(0).max(1),
key_topics: z.array(z.string()),
summary: z.string(),
}),
prompt: "Analyze this customer review: ...",
});
Works identically with OpenAI, Google, Mistral — change one line.
Use Cases
Data Extraction
From emails:
const schema = z.object({
sender: z.string(),
intent: z.enum(["inquiry", "complaint", "order", "feedback"]),
urgency: z.enum(["low", "medium", "high"]),
action_required: z.string(),
deadline: z.string().nullable(),
});
From invoices/receipts:
const schema = z.object({
vendor: z.string(),
date: z.string(),
items: z.array(z.object({
description: z.string(),
quantity: z.number(),
unit_price: z.number(),
total: z.number(),
})),
subtotal: z.number(),
tax: z.number(),
total: z.number(),
});
Classification
const schema = z.object({
category: z.enum(["bug", "feature_request", "question", "documentation"]),
priority: z.enum(["critical", "high", "medium", "low"]),
affected_component: z.string(),
reproducible: z.boolean(),
});
Feed support tickets → get structured triage data → route automatically.
Content Generation
const schema = z.object({
title: z.string().max(60),
meta_description: z.string().max(160),
h2_headings: z.array(z.string()),
target_keyword: z.string(),
word_count_target: z.number(),
content_type: z.enum(["how-to", "listicle", "comparison", "review"]),
});
Generate content briefs that feed directly into your CMS or content pipeline.
API Responses
Build AI-powered API endpoints that always return valid, typed responses:
// POST /api/analyze
const analysis = await generateObject({
model: anthropic("claude-sonnet-4-20250514"),
schema: responseSchema,
prompt: `Analyze this data: ${JSON.stringify(requestBody)}`,
});
return Response.json(analysis.object);
Your API consumers get guaranteed response shapes. No "sometimes the AI returns X, sometimes Y."
Best Practices
1. Keep Schemas Focused
Good: Extract 5-10 specific fields from a document. Bad: Generate a 50-field object with nested arrays of nested objects.
Simpler schemas = higher accuracy. If you need complex output, break it into multiple structured calls.
2. Use Enums for Categories
// Good: constrained options
category: z.enum(["bug", "feature", "question"])
// Bad: free-form string
category: z.string() // Returns anything: "Bug", "bug report", "software defect"
Enums guarantee consistent values your code can switch on.
3. Add Descriptions
const schema = z.object({
sentiment: z.enum(["positive", "negative", "neutral"])
.describe("Overall sentiment of the text"),
confidence: z.number().min(0).max(1)
.describe("How confident the model is in the sentiment classification"),
});
Descriptions guide the model toward more accurate values.
4. Handle Edge Cases with Nullable
const schema = z.object({
email: z.string().email().nullable(), // Might not be present
phone: z.string().nullable(),
deadline: z.string().nullable(),
});
Use .nullable() for fields that might not exist in the input data.
Structured Outputs vs JSON Mode vs Prompting
| Approach | Reliability | Speed | Complexity |
|---|---|---|---|
| Structured Outputs | ~100% valid | Slightly slower | Schema definition |
| JSON Mode | ~95% valid JSON (schema not guaranteed) | Normal | Simple |
| "Please respond in JSON" | ~70-80% valid | Normal | None |
Always use structured outputs when you need machine-readable data. "Please respond in JSON" is for prototyping only.
FAQ
Do structured outputs reduce quality?
Slightly. Constraining the output format limits the model's expressiveness. For extraction and classification: no meaningful quality loss. For creative generation: some loss. Use structured outputs for data tasks, free-form for creative tasks.
Which models support structured outputs?
All major models (GPT-4o, Claude Sonnet/Opus, Gemini Pro) support structured outputs. Implementation varies by provider — use the Vercel AI SDK for a unified API.
How do structured outputs handle ambiguity?
If the input doesn't clearly map to a schema field, the model makes its best judgment. Use .nullable() for optional fields and .describe() to clarify what each field should contain.
Can I use structured outputs with streaming?
Yes. The Vercel AI SDK supports streamObject() which streams partial objects as they're generated. Useful for showing incremental results in a UI.
What's the performance impact?
Structured outputs add 10-20% latency compared to free-form generation (due to constrained decoding). Negligible for most applications. The reliability improvement far outweighs the latency cost.
Bottom Line
Structured outputs are the single most important feature for building production AI applications. They turn AI from "unpredictable text generator" into "reliable data processing engine." Every AI application that needs machine-readable output should use structured outputs — no exceptions.
Start with: Replace one JSON.parse(aiResponse) call with generateObject() using a Zod schema. See zero parsing errors. Apply everywhere you need structured AI output.