Structured Outputs
Advanced 13 min read Structured Outputs

Type-Safe TypeScript

One contract, no drift

One schema → TypeScript types, runtime validators and model constraints together. Type safety removes the sharp boundary between 'the model's output' and 'my code's types'.

typescripttype safetyzodinferenceschema-to-typecodegen
Prerequisites: Validation & Repair
Before we start

What you will learn

  • Derive types+validators from one schema
  • Eliminate as-casts on model output
  • Keep consumers type-aligned as schemas evolve
Introduction

What is this?

Type-safe TS derives compile-time types and runtime validators from a single schema (Zod works well). Model outputs pass through the validator once, arriving in consumers as precisely-typed values — no as-casts, no schema drift between frontend, backend and eval. The schema is the contract; TS simply enforces it everywhere else.

Motivation

Why do you need it?

  • as-casts on JSON.parse silence errors the validator would have caught.
  • One schema drives the model prompt, validator and DTOs — drift is impossible by construction.
  • IDE/compile errors find schema consumers when fields change.
  • Eval fixtures typed from the same schema catch mismatches early.
Think about it

Mental model

Imagine

One master meal plan feeding every kitchen

Think of

Shared schema

The chef (model) cooks to the master plan; each kitchen station (module) reads the same plan (schema) to plate correctly. Nobody rewrites the plan per station, so the dish never differs across rooms. A mistyped station now fails loudly instead of silently serving odd portions.

Mechanics

How it works

  • Define the schema once (e.g. Zod object).
  • Infer the TS type with z.infer.
  • Validate parse-time: schema.parse(rawJSON) → typed data or thrown error.
  • Use the same schema to build the model's structured-output constraint.
  • Consumers import type + validator; compile catches drift.

One schema driving model, runtime and types.

Schema
z.infer
parse()
model constraint
Consumers
Hands-on

Examples

Schema → type → guard, unified

Schema → type → guard, unified
import { z } from "zod";
export const TicketSchema = z.object({
intent: z.enum(["refund", "complaint", "question"]),
confidence: z.number().min(0).max(1),
tags: z.array(z.string()).max(10),
});
export type Ticket = z.infer<typeof TicketSchema>;
// model call uses TicketSchema; consumers use Ticket type
function handle(rawJson: unknown): Ticket {
return TicketSchema.parse(rawJson); // throws on mismatch, typed afterwards
}

The schema feeds the model constraint AND runtime validation AND static types.

Real world

In production

  • Route handlers parse LLM blobs via the schema → typed handlers.
  • React UIs receive Ticket types, rendering enums as typed components.
  • Eval suites compare generated data with the shared schema.
Guidelines

Best practices

  • Single source of schema truth per system boundary.
  • Prefer schema.parse over safeParse + as-cast where failures must raise.
  • Export types from schema modules, never redefine manually.
  • Generate fixtures/tests from the schema when it changes.
Watch out

Common mistakes

  • Type = hand-copy of schema; validator = another copy; prompt = third — three truths.
  • as/unknown casts that void the validator's guarantees.
  • Optional-vs-nullable confusion leaking through types.
Root cause analysis

Common issues

Error
Check: typed boundaries but 'any' everywhere
Root cause

Missing validators at ingestion

Fix

Enforce parse at the API boundary; forbid implicit any on model output.

Error
Schema changes break the frontend
Root cause

Consumers not regenerated/imported

Fix

Derive all consumers from the shared schema module.

When things break

Debugging tips

  • Type error after schema edit → consumers compile-free because they use z.infer.
  • Runtime error unreachable in types → double check a cast is not lurking.
  • UI shows raw model blob (any) → the route dropped the schema at the boundary.
Interviews

Interview checkpoint

Practice

Hands-on lab

Schema → Type → Guard One-Pager
30 min · intermediate
In progress
Objectives
  • Define a Zod schema
  • Infer the type and validate a payload
  • See the compile error move with the schema

Schema → Type → Guard One-Pager

30 min · intermediate

0/4 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/2
1

z.infer derives…

2

Trusting a cast instead of a validator risks typed-but-wrong data.

Go deeper

Official documentation