1> ## Documentation Index
2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
3> Use this file to discover all available pages before exploring further.
4
5# Get structured output from agents
6
7> Return validated JSON from agent workflows using JSON Schema, Zod, or Pydantic. Get type-safe, structured data after multi-turn tool use.
8
9Structured outputs let you define the exact shape of data you want back from an agent. The agent can use any tools it needs to complete the task, and you still get validated JSON matching your schema at the end. Define a [JSON Schema](https://json-schema.org/understanding-json-schema/about) for the structure you need, and the SDK validates the output against it, re-prompting on mismatch. If validation does not succeed within the retry limit, the result is an error instead of structured data; see [Error handling](#error-handling).
10
11For full type safety, use [Zod](#type-safe-schemas-with-zod-and-pydantic) (TypeScript) or [Pydantic](#type-safe-schemas-with-zod-and-pydantic) (Python) to define your schema and get strongly-typed objects back.
12
13## Why structured outputs?
14
15Agents return free-form text by default, which works for chat but not when you need to use the output programmatically. Structured outputs give you typed data you can pass directly to your application logic, database, or UI components.
16
17Consider a recipe app where an agent searches the web and brings back recipes. Without structured outputs, you get free-form text that you'd need to parse yourself. With structured outputs, you define the shape you want and get typed data you can use directly in your app.
18
19<AccordionGroup>
20 <Accordion title="Without structured outputs">
21 ```text theme={null}
22 Here's a classic chocolate chip cookie recipe!
23
24 **Chocolate Chip Cookies**
25 Prep time: 15 minutes | Cook time: 10 minutes
26
27 Ingredients:
28 - 2 1/4 cups all-purpose flour
29 - 1 cup butter, softened
30 ...
31 ```
32
33 To use this in your app, you'd need to parse out the title, convert "15 minutes" to a number, separate ingredients from instructions, and handle inconsistent formatting across responses.
34 </Accordion>
35
36 <Accordion title="With structured outputs">
37 ```json theme={null}
38 {
39 "name": "Chocolate Chip Cookies",
40 "prep_time_minutes": 15,
41 "cook_time_minutes": 10,
42 "ingredients": [
43 { "item": "all-purpose flour", "amount": 2.25, "unit": "cups" },
44 { "item": "butter, softened", "amount": 1, "unit": "cup" }
45 // ...
46 ],
47 "steps": ["Preheat oven to 375°F", "Cream butter and sugar" /* ... */]
48 }
49 ```
50
51 Typed data you can use directly in your UI.
52 </Accordion>
53</AccordionGroup>
54
55## Quick start
56
57To use structured outputs, define a [JSON Schema](https://json-schema.org/understanding-json-schema/about) describing the shape of data you want, then pass it to `query()` via the `outputFormat` option (TypeScript) or `output_format` option (Python). When the agent finishes, the result message includes a `structured_output` field with validated data matching your schema.
58
59The example below asks the agent to research Anthropic and return the company name, year founded, and headquarters as structured output.
60
61<CodeGroup>
62 ```typescript TypeScript theme={null}
63 import { query } from "@anthropic-ai/claude-agent-sdk";
64
65 // Define the shape of data you want back
66 const schema = {
67 type: "object",
68 properties: {
69 company_name: { type: "string" },
70 founded_year: { type: "number" },
71 headquarters: { type: "string" }
72 },
73 required: ["company_name"]
74 };
75
76 for await (const message of query({
77 prompt: "Research Anthropic and provide key company information",
78 options: {
79 outputFormat: {
80 type: "json_schema",
81 schema: schema
82 }
83 }
84 })) {
85 // The result message contains structured_output with validated data
86 if (message.type === "result" && message.subtype === "success" && message.structured_output) {
87 console.log(message.structured_output);
88 // { company_name: "Anthropic", founded_year: 2021, headquarters: "San Francisco, CA" }
89 }
90 }
91 ```
92
93 ```python Python theme={null}
94 import asyncio
95 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
96
97 # Define the shape of data you want back
98 schema = {
99 "type": "object",
100 "properties": {
101 "company_name": {"type": "string"},
102 "founded_year": {"type": "number"},
103 "headquarters": {"type": "string"},
104 },
105 "required": ["company_name"],
106 }
107
108
109 async def main():
110 async for message in query(
111 prompt="Research Anthropic and provide key company information",
112 options=ClaudeAgentOptions(
113 output_format={"type": "json_schema", "schema": schema}
114 ),
115 ):
116 # The result message contains structured_output with validated data
117 if isinstance(message, ResultMessage) and message.structured_output:
118 print(message.structured_output)
119 # {'company_name': 'Anthropic', 'founded_year': 2021, 'headquarters': 'San Francisco, CA'}
120
121
122 asyncio.run(main())
123 ```
124</CodeGroup>
125
126## Type-safe schemas with Zod and Pydantic
127
128Instead of writing JSON Schema by hand, you can use [Zod](https://zod.dev/) (TypeScript) or [Pydantic](https://docs.pydantic.dev/latest/) (Python) to define your schema. These libraries generate the JSON Schema for you and let you parse the response into a fully-typed object you can use throughout your codebase with autocomplete and type checking.
129
130The example below defines a schema for a feature implementation plan with a summary, list of steps (each with complexity level), and potential risks. The agent plans the feature and returns a typed `FeaturePlan` object. You can then access properties like `plan.summary` and iterate over `plan.steps` with full type safety.
131
132<CodeGroup>
133 ```typescript TypeScript theme={null}
134 import { z } from "zod";
135 import { query } from "@anthropic-ai/claude-agent-sdk";
136
137 // Define schema with Zod
138 const FeaturePlan = z.object({
139 feature_name: z.string(),
140 summary: z.string(),
141 steps: z.array(
142 z.object({
143 step_number: z.number(),
144 description: z.string(),
145 estimated_complexity: z.enum(["low", "medium", "high"])
146 })
147 ),
148 risks: z.array(z.string())
149 });
150
151 type FeaturePlan = z.infer<typeof FeaturePlan>;
152
153 // Convert to JSON Schema
154 const schema = z.toJSONSchema(FeaturePlan);
155
156 // Use in query
157 for await (const message of query({
158 prompt:
159 "Plan how to add dark mode support to a React app. Break it into implementation steps.",
160 options: {
161 outputFormat: {
162 type: "json_schema",
163 schema: schema
164 }
165 }
166 })) {
167 if (message.type === "result" && message.subtype === "success" && message.structured_output) {
168 // Validate and get fully typed result
169 const parsed = FeaturePlan.safeParse(message.structured_output);
170 if (parsed.success) {
171 const plan: FeaturePlan = parsed.data;
172 console.log(`Feature: ${plan.feature_name}`);
173 console.log(`Summary: ${plan.summary}`);
174 plan.steps.forEach((step) => {
175 console.log(`${step.step_number}. [${step.estimated_complexity}] ${step.description}`);
176 });
177 }
178 }
179 }
180 ```
181
182 ```python Python theme={null}
183 import asyncio
184 from pydantic import BaseModel
185 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
186
187
188 class Step(BaseModel):
189 step_number: int
190 description: str
191 estimated_complexity: str # 'low', 'medium', 'high'
192
193
194 class FeaturePlan(BaseModel):
195 feature_name: str
196 summary: str
197 steps: list[Step]
198 risks: list[str]
199
200
201 async def main():
202 async for message in query(
203 prompt="Plan how to add dark mode support to a React app. Break it into implementation steps.",
204 options=ClaudeAgentOptions(
205 output_format={
206 "type": "json_schema",
207 "schema": FeaturePlan.model_json_schema(),
208 }
209 ),
210 ):
211 if isinstance(message, ResultMessage) and message.structured_output:
212 # Validate and get fully typed result
213 plan = FeaturePlan.model_validate(message.structured_output)
214 print(f"Feature: {plan.feature_name}")
215 print(f"Summary: {plan.summary}")
216 for step in plan.steps:
217 print(
218 f"{step.step_number}. [{step.estimated_complexity}] {step.description}"
219 )
220
221
222 asyncio.run(main())
223 ```
224</CodeGroup>
225
226**Benefits:**
227
228* Full type inference (TypeScript) and type hints (Python)
229* Runtime validation with `safeParse()` or `model_validate()`
230* Better error messages
231* Composable, reusable schemas
232
233## Output format configuration
234
235The `outputFormat` (TypeScript) or `output_format` (Python) option accepts an object with:
236
237* `type`: Set to `"json_schema"` for structured outputs
238* `schema`: A [JSON Schema](https://json-schema.org/understanding-json-schema/about) object defining your output structure. You can generate this from a Zod schema with `z.toJSONSchema()` or a Pydantic model with `.model_json_schema()`
239
240The SDK supports standard JSON Schema features including all basic types (object, array, string, number, boolean, null), `enum`, `const`, `required`, nested objects, and `$ref` definitions. For the full list of supported features and limitations, see [JSON Schema limitations](https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-schema-limitations).
241
242## Example: TODO tracking agent
243
244This example demonstrates how structured outputs work with multi-step tool use. The agent needs to find TODO comments in the codebase, then look up git blame information for each one. It autonomously decides which tools to use (Grep to search, Bash to run git commands) and combines the results into a single structured response.
245
246The schema includes optional fields (`author` and `date`) since git blame information might not be available for all files. The agent fills in what it can find and omits the rest.
247
248<CodeGroup>
249 ```typescript TypeScript theme={null}
250 import { query } from "@anthropic-ai/claude-agent-sdk";
251
252 // Define structure for TODO extraction
253 const todoSchema = {
254 type: "object",
255 properties: {
256 todos: {
257 type: "array",
258 items: {
259 type: "object",
260 properties: {
261 text: { type: "string" },
262 file: { type: "string" },
263 line: { type: "number" },
264 author: { type: "string" },
265 date: { type: "string" }
266 },
267 required: ["text", "file", "line"]
268 }
269 },
270 total_count: { type: "number" }
271 },
272 required: ["todos", "total_count"]
273 };
274
275 // Agent uses Grep to find TODOs, Bash to get git blame info
276 for await (const message of query({
277 prompt: "Find all TODO comments in this codebase and identify who added them",
278 options: {
279 outputFormat: {
280 type: "json_schema",
281 schema: todoSchema
282 }
283 }
284 })) {
285 if (message.type === "result" && message.subtype === "success" && message.structured_output) {
286 const data = message.structured_output as { total_count: number; todos: Array<{ file: string; line: number; text: string; author?: string; date?: string }> };
287 console.log(`Found ${data.total_count} TODOs`);
288 data.todos.forEach((todo) => {
289 console.log(`${todo.file}:${todo.line} - ${todo.text}`);
290 if (todo.author) {
291 console.log(` Added by ${todo.author} on ${todo.date}`);
292 }
293 });
294 }
295 }
296 ```
297
298 ```python Python theme={null}
299 import asyncio
300 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
301
302 # Define structure for TODO extraction
303 todo_schema = {
304 "type": "object",
305 "properties": {
306 "todos": {
307 "type": "array",
308 "items": {
309 "type": "object",
310 "properties": {
311 "text": {"type": "string"},
312 "file": {"type": "string"},
313 "line": {"type": "number"},
314 "author": {"type": "string"},
315 "date": {"type": "string"},
316 },
317 "required": ["text", "file", "line"],
318 },
319 },
320 "total_count": {"type": "number"},
321 },
322 "required": ["todos", "total_count"],
323 }
324
325
326 async def main():
327 # Agent uses Grep to find TODOs, Bash to get git blame info
328 async for message in query(
329 prompt="Find all TODO comments in this codebase and identify who added them",
330 options=ClaudeAgentOptions(
331 output_format={"type": "json_schema", "schema": todo_schema}
332 ),
333 ):
334 if isinstance(message, ResultMessage) and message.structured_output:
335 data = message.structured_output
336 print(f"Found {data['total_count']} TODOs")
337 for todo in data["todos"]:
338 print(f"{todo['file']}:{todo['line']} - {todo['text']}")
339 if "author" in todo:
340 print(f" Added by {todo['author']} on {todo['date']}")
341
342
343 asyncio.run(main())
344 ```
345</CodeGroup>
346
347## Error handling
348
349Structured output generation can fail when the agent cannot produce valid JSON matching your schema. This typically happens when the schema is too complex for the task, the task itself is ambiguous, or the agent hits its retry limit trying to fix validation errors. It can also happen without any validation failure: a [model fallback](/en/model-config#automatic-model-fallback) can retract an already-completed output mid-stream, and if no retry replaces it the run ends with the same error. Check the result's `errors` text to tell the two causes apart before debugging your schema.
350
351When an error occurs, the result message has a `subtype` indicating what went wrong:
352
353| Subtype | Meaning |
354| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
355| `success` | Output was generated and validated successfully |
356| `error_max_structured_output_retries` | No valid output survived after multiple attempts (validation failures, or a model-fallback retraction with no successful retry) |
357
358The example below checks the `subtype` field to determine whether the output was generated successfully or if you need to handle a failure:
359
360<CodeGroup>
361 ```typescript TypeScript theme={null}
362 for await (const msg of query({
363 prompt: "Extract contact info from the document",
364 options: {
365 outputFormat: {
366 type: "json_schema",
367 schema: contactSchema
368 }
369 }
370 })) {
371 if (msg.type === "result") {
372 if (msg.subtype === "success" && msg.structured_output) {
373 // Use the validated output
374 console.log(msg.structured_output);
375 } else if (msg.subtype === "error_max_structured_output_retries") {
376 // Handle the failure - retry with simpler prompt, fall back to unstructured, etc.
377 console.error("Could not produce valid output");
378 }
379 }
380 }
381 ```
382
383 ```python Python theme={null}
384 async for message in query(
385 prompt="Extract contact info from the document",
386 options=ClaudeAgentOptions(
387 output_format={"type": "json_schema", "schema": contact_schema}
388 ),
389 ):
390 if isinstance(message, ResultMessage):
391 if message.subtype == "success" and message.structured_output:
392 # Use the validated output
393 print(message.structured_output)
394 elif message.subtype == "error_max_structured_output_retries":
395 # Handle the failure
396 print("Could not produce valid output")
397 ```
398</CodeGroup>
399
400**Tips for avoiding errors:**
401
402* **Keep schemas focused.** Deeply nested schemas with many required fields are harder to satisfy. Start simple and add complexity as needed.
403* **Match schema to task.** If the task might not have all the information your schema requires, make those fields optional.
404* **Use clear prompts.** Ambiguous prompts make it harder for the agent to know what output to produce.
405
406## Related resources
407
408* [JSON Schema documentation](https://json-schema.org/): learn JSON Schema syntax for defining complex schemas with nested objects, arrays, enums, and validation constraints
409* [API Structured Outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs): use structured outputs with the Claude API directly for single-turn requests without tool use
410* [Custom tools](/en/agent-sdk/custom-tools): give your agent custom tools to call during execution before returning structured output