Structured Outputs in the Claude Agent SDK
Get typed, schema-validated objects back instead of text. Define a Zod schema, configure outputFormat, and build data extraction pipelines that feed directly into your workflows.
When text responses are not enough
Most agent responses are text. The agent researches something, writes a summary, formats it for the reader. That works well for deliverables a human reads.
But sometimes you need the data itself — structured, parseable, ready to load into a spreadsheet, database, or downstream workflow. Asking the agent to "respond in JSON" and then parsing the text output is fragile. The agent might add explanation text before or after the JSON. Field names might vary between runs.
Structured output mode solves this. It tells the SDK to enforce a specific schema on the agent's response. You get back a validated object, not a text string.
Defining the schema with Zod
Structured outputs use the same Zod library you used for custom tools. Define the shape of the data you want:
import { z } from "zod";
const CompanyInfo = z.object({
name: z.string(),
founded: z.number(),
keyProducts: z.array(z.string()),
description: z.string(),
});
type CompanyInfoType = z.infer<typeof CompanyInfo>;
The z.infer utility gives you the TypeScript type automatically. You do not have to define it separately.
Configuring the output format
Pass the schema as an outputFormat option in your query:
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
for await (const message of query(messages(), {
model: "claude-sonnet-4-5",
allowedTools: ["web_search", "web_fetch"],
outputFormat: {
type: "json_schema",
jsonSchema: zodToJsonSchema(CompanyInfo, { target: "draft-07" }),
},
permissionMode: "bypassPermissions",
dangerouslyAllowBypassPermissions: true,
})) {
if (message.type === "result" && message.subtype === "success") {
const parsed = CompanyInfo.safeParse(message.structuredOutput);
if (parsed.success) {
console.log(parsed.data);
}
}
}
Two things to note here:
The { target: "draft-07" } argument is required. Pass zodToJsonSchema(schema, { target: "draft-07" }) — without this second argument, the SDK will error out. This is a known issue in the current SDK version.
Access the structured output via message.structuredOutput. This field contains the raw parsed object. Run it through CompanyInfo.safeParse() to validate it against your schema and get typed access to the data.
A practical research pattern
Research five companies, get structured data for each:
const companies = ["Anthropic", "OpenAI", "Mistral", "Cohere", "AI21 Labs"];
const results: CompanyInfoType[] = [];
for (const company of companies) {
async function* messages() {
yield {
role: "user" as const,
content: `Research ${company} and extract the key company information.`,
};
}
for await (const message of query(messages(), {
model: "claude-sonnet-4-5",
allowedTools: ["web_search", "web_fetch"],
outputFormat: {
type: "json_schema",
jsonSchema: zodToJsonSchema(CompanyInfo, { target: "draft-07" }),
},
permissionMode: "bypassPermissions",
dangerouslyAllowBypassPermissions: true,
})) {
if (message.type === "result" && message.subtype === "success") {
const parsed = CompanyInfo.safeParse(message.structuredOutput);
if (parsed.success) results.push(parsed.data);
}
}
}
// results is now an array of CompanyInfoType objects
// Load into a spreadsheet, database, or reporting pipeline
This pattern turns a research workflow into a data extraction pipeline. The agent searches the web for each company, extracts the information you specified, and returns structured data ready for downstream use.
> Where this fits in operator work: Competitive analysis, client research, market scanning — any workflow where you need to extract the same set of fields from multiple sources. Instead of reading a wall of text and pulling the data manually, the agent returns the fields you care about in the format you specified. The data goes directly into your deliverable.
---
Author: FractionalSkill