Structured Output
Structured output lets an agent return an object that matches the shape defined by a schema instead of returning text. The schema tells the model what fields to produce, and the model ensures the final result fits that shape.
When to use structured outputDirect link to When to use structured output
Use structured output when you need an agent to return a data object rather than text. Having well defined fields can make it simpler to pull out the values you need for API calls, UI rendering, or application logic.
Defining schemasDirect link to Defining schemas
Agents can return structured data by defining the expected output with either Zod or JSON Schema. Zod is recommended because it provides TypeScript type inference and runtime validation, while JSON Schema is useful when you need a language agnostic format.
- Zod
- JSON Schema
Define the output shape using Zod:
import { z } from "zod";
const response = await testAgent.generate("Help me plan my day.", {
structuredOutput: {
schema: z.array(
z.object({
name: z.string(),
activities: z.array(z.string()),
}),
),
},
});
console.log(response.object);
You can also use JSON Schema to define your output structure:
const response = await testAgent.generate("Help me plan my day.", {
structuredOutput: {
schema: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
activities: {
type: "array",
items: { type: "string" },
},
},
required: ["name", "activities"],
},
},
},
});
console.log(response.object);
See .generate() for a full list of configuration options.
Example outputDirect link to Example output
The response.object will contain the structured data as defined by the schema.
[
{
"name": "Morning Routine",
"activities": ["Wake up at 7am", "Exercise", "Shower", "Breakfast"]
},
{
"name": "Work",
"activities": ["Check emails", "Team meeting", "Lunch break"]
},
{
"name": "Evening",
"activities": ["Dinner", "Relax", "Read a book", "Sleep by 10pm"]
}
]
StreamingDirect link to Streaming
Streaming also supports structured output. The final structured object is available on stream.fullStream and after the stream completes on stream.object. Text stream chunks are still emitted, but they contain natural language text rather than structured data.
import { z } from "zod";
const stream = await testAgent.stream("Help me plan my day.", {
structuredOutput: {
schema: z.array(
z.object({
name: z.string(),
activities: z.array(z.string())
})
),
},
});
for await (const chunk of stream.fullStream) {
if (chunk.type === "object-result") {
console.log("\n", JSON.stringify(chunk, null, 2));
}
process.stdout.write(JSON.stringify(chunk));
}
console.log(await stream.object)
for await (const chunk of stream.textStream) {
process.stdout.write(chunk);
}
Structuring agentDirect link to Structuring agent
When your main agent isn't proficient at creating structured output you can provide a model to structuredOutput. In this case, Mastra uses a second agent under the hood to extract structured data from the main agent's natural language response. This makes two LLM calls, one to generate the response and another to turn that response into the structured object, which adds some latency and cost but can improve accuracy for complex structuring tasks.
import { z } from "zod";
const response = await testAgent.generate("Analyze the TypeScript programming language.", {
structuredOutput: {
schema: z.object({
overview: z.string(),
strengths: z.array(z.string()),
weaknesses: z.array(z.string()),
useCases: z.array(z.object({
scenario: z.string(),
reasoning: z.string(),
})),
comparison: z.object({
similarTo: z.array(z.string()),
differentiators: z.array(z.string()),
}),
}),
model: "openai/gpt-4o",
},
});
console.log(response.object);
Response formatDirect link to Response format
By default, Mastra passes the schema to the model provider using the response_format API parameter. Most model providers have built-in support for this, which reliably enforces the schema.
If your model provider doesn't support response_format, you'll get an error from the API. When this happens, set jsonPromptInjection: true. This adds the schema to the system prompt instead, instructing the model to output JSON. This is less reliable than the API parameter approach.
import { z } from "zod";
const response = await testAgent.generate("Help me plan my day.", {
structuredOutput: {
schema: z.array(
z.object({
name: z.string(),
activities: z.array(z.string()),
}),
),
jsonPromptInjection: true,
},
});
console.log(response.object);
Gemini 2.5 models do not support combining response_format (structured output) with function calling (tools) in the same API call. If your agent has tools and you're using structuredOutput with a Gemini 2.5 model, you must set jsonPromptInjection: true to avoid the error Function calling with a response mime type: 'application/json' is unsupported.
const response = await agentWithTools.generate("Your prompt", {
structuredOutput: {
schema: yourSchema,
jsonPromptInjection: true, // Required for Gemini 2.5 when tools are present
},
});
Error handlingDirect link to Error handling
When schema validation fails, you can control how errors are handled using errorStrategy. The default strict strategy throws an error, while warn logs a warning and continues. The fallback strategy returns the values provided using fallbackValue.
import { z } from "zod";
const response = await testAgent.generate("Tell me about TypeScript.", {
structuredOutput: {
schema: z.object({
summary: z.string(),
keyFeatures: z.array(z.string())
}),
errorStrategy: "fallback",
fallbackValue: {
summary: "TypeScript is a typed superset of JavaScript",
keyFeatures: ["Static typing", "Compiles to JavaScript", "Better tooling"]
}
}
});
console.log(response.object);