Get structured output from LLMs with Anthropic
Anthropic's Claude models support structured output through tool use with forced tool calls. Define a schema as a tool, force Claude to use it, and extract the structured data from the tool call arguments.
Prerequisites
- +Node 20+ or Python 3.10+
- +Anthropic API key
- +Understanding of Zod or JSON Schema
Step-by-Step
- 1
Install the SDK
The official Anthropic SDK handles schema conversion.
pnpm add @anthropic-ai/sdk zod - 2
Define a tool for structured extraction
Create a tool whose input schema matches your desired output structure.
import Anthropic from '@anthropic-ai/sdk'; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; const ExtractedData = z.object({ name: z.string(), email: z.string().email(), company: z.string(), interests: z.array(z.string()), }); const tool = { name: 'extract_contact', description: 'Extract structured contact information from text', input_schema: zodToJsonSchema(ExtractedData), }; - 3
Force the tool call
Use tool_choice to make Claude always use your extraction tool.
const client = new Anthropic(); const message = await client.messages.create({ model: 'claude-sonnet-4-5-20250514', max_tokens: 1024, tools: [tool], tool_choice: { type: 'tool', name: 'extract_contact' }, messages: [{ role: 'user', content: 'Extract contact info: John Smith, john@acme.com, works at Acme Corp, interested in AI and ML' }], }); - 4
Parse the tool call arguments
The structured data is in the tool_use content block's input field.
const toolUse = message.content.find((b) => b.type === 'tool_use'); if (toolUse && toolUse.type === 'tool_use') { const data = ExtractedData.parse(toolUse.input); console.log(data.name, data.email); } - 5
Add validation with Zod
Always validate the extracted data against your schema. Claude follows schemas closely but validation adds safety.
try { const validated = ExtractedData.parse(toolUse.input); } catch (e) { console.error('Extraction failed validation:', e); } - 6
Handle complex nested structures
Claude handles nested objects and arrays well. Keep descriptions clear for each field.
const Order = z.object({ customer: z.object({ name: z.string(), address: z.string() }), items: z.array(z.object({ product: z.string(), quantity: z.number(), price: z.number() })), total: z.number(), });
Common Pitfalls
- !Forgetting tool_choice - without it, Claude may respond with text instead of using the tool.
- !Overly complex schemas without descriptions. Claude needs context to fill fields correctly.
- !Not handling the case where the model cannot extract required fields (validation will fail).
- !Using stop_sequences that interrupt the tool call JSON.
DevDigest Academy
Structured AI engineering courses with hands-on labs. Build production-ready apps faster.
What's Next
- ->Build extraction chains that process documents in batches.
- ->Combine with Claude's extended thinking for complex extraction tasks.
- ->Add post-processing validation for business rules beyond schema.
