← Back to articles

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

ProviderFeatureMethod
OpenAIStructured Outputsresponse_format: { type: "json_schema", json_schema: {...} }
AnthropicTool use for structured outputDefine a tool with the desired schema
GoogleJSON moderesponse_mime_type: "application/json"
Vercel AI SDKgenerateObject()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

ApproachReliabilitySpeedComplexity
Structured Outputs~100% validSlightly slowerSchema definition
JSON Mode~95% valid JSON (schema not guaranteed)NormalSimple
"Please respond in JSON"~70-80% validNormalNone

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.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.