← Course index

Structured outputs with Zod

generateText with Output.object enforces a Zod schema so the model returns JSON you can trust in TypeScript.

Day 4: Structured output·~18 min read

On this page

After this topic

You will stop asking the model for “JSON only” and hoping. You will have a Zod schema, a generateText call with Output.object, and a typed object you can save to a database — or a loud failure if the model cannot fill the shape.

Likely text is not a contract

The model writes likely text. “Looks like JSON” is not the same as “is JSON your TypeScript can trust.”

If the next line of code needs fields — name, role, a list of tasks — you need a schema, not a prompt that says “return JSON”.

You already know the model writes likely text. If you ask it for JSON in the prompt — “return JSON only” — you still get likely text. Often that is JSON. Often it is JSON wrapped in markdown fences. Often it has a preface (“Sure, here is…”). JSON.parse throws. Your form never fills.

Production features that need data — a profile object, a list of tasks, a routing decision — cannot afford a string you hope is JSON. They need a contract: these keys, these types, or fail loud.

This is the same Day 1 lesson with a new surface. Constraining the guess is your job. A schema is a constraint the SDK can enforce. A sentence in the prompt is a suggestion the model can ignore.

Prose is not a contract. Ask for JSON in the prompt and you still get likely text — fences, prefaces, missing keys. Output.object returns a typed object that matches Zod, or it fails loud.
  1. 01

    JSON.parse on a guess

    The model often wraps JSON in ```json fences or adds a sentence. That throws. Output.object exists so you do not police that.

generateText + Output.object

In AI SDK v6, structured data is still generateText. You pass an output option, not a separate generateObject call (that API is gone). Output.object takes a Zod schema. You read result.output — parsed, typed, or the call failed.

The schema is the shape. The prompt is the instruction. Keep both small. A 40-field object will be slow, expensive, and more likely to fail validation. Split work across steps if you must.

Providers that support native structured output will use it. Providers that do not still get a validated object — the SDK retries or repairs according to the current docs. You write one call either way.

  1. 01

    Zod is the contract

    z.object({…}) is runtime validation and TypeScript types. The model is steered to fill those fields. You do not regex the keys out.

  2. 02

    output, not object

    v6 returns { output } from generateText. If the model cannot satisfy the schema, the SDK fails loud — better than a half-valid string in production.

  3. 03

    prompt is still just text

    The schema does not replace the prompt. It constrains the completion. Say what you want; the schema says what shape it must have.

When the next line needs fields

Use Output.object when code will read properties: save to Postgres, render a card, pick a branch, call another API. The user never sees the JSON. They see a form that filled itself, or a ticket that was created.

Use plain generateText or streamText when the user is meant to read an essay. Streaming a half-object onto the screen is worse than waiting for a valid one.

Enums and small unions are gold. z.enum(['billing', 'bug', 'other']) as a routing decision is a better product than a free-text “category” you then parse with more AI.

  • Data the code will useOutput.object
  • Prose the user will readgenerateText / streamText
  • A choice among known optionsz.enum, not a paragraph

Trade-offs

Larger schemas cost more tokens and more latency. Prefer the smallest object that unblocks the next step. If validation keeps failing, the prompt is unclear or the schema is too tight — fix those before retrying forever.

Nested objects and long arrays are where models get sloppy. Flatten. Cap array length in the schema. Two small generateText calls beat one giant blob.

Failures should be loud. Catch them, show the user a retry, log the raw error. Do not JSON.parse a consolation string “just in case.” That is how you ship a profile named ```json.

Common questions

What is “Structured outputs with Zod”?
generateText with Output.object enforces a Zod schema so the model returns JSON you can trust in TypeScript.
What will I be able to do after this lesson?
You will stop asking the model for “JSON only” and hoping. You will have a Zod schema, a generateText call with Output.object, and a typed object you can save to a database — or a loud failure if the model cannot fill the shape.
How long does this lesson take?
About 18 minutes of reading. It is a free chapter in the AI SDK Patterns TypeScript course.
When should I not use this?
Larger schemas cost more tokens and more latency. Prefer the smallest object that unblocks the next step. If validation keeps failing, the prompt is unclear or the schema is too tight — fix those before retrying forever.

Written by Akash Panchal·Updated August 29, 2026