Schema design ​
Treat the schema as the contract product code will trust after validation.
Define a narrow schema ​
pnpm add zodimport { z } from 'zod'
export const ticketSchema = z.object({
category: z.enum(['billing', 'technical', 'account']),
priority: z.enum(['low', 'normal', 'high']),
summary: z.string().min(1),
needsHumanReview: z.boolean(),
})
export type Ticket = z.infer<typeof ticketSchema>Prefer enums, booleans, bounded numbers, and required strings over vague fields or deeply nested objects. Narrow schemas are easier for models to produce and applications to test.
Describe ambiguous fields ​
const escalationSchema = z.object({
reason: z.string().describe(
'Short reason the ticket should be escalated.',
),
severity: z.enum(['normal', 'urgent']),
}).describe(...) becomes JSON Schema description metadata and can guide provider generation. It does not replace validation or product rules.
Use .meta({ title: 'Escalation' }) on a root schema when a stable schema name is useful. Do not rely on a .metadata(...) method or arbitrary provider-specific metadata.
Keep schemas portable ​
Provider adapters translate Zod into provider output schemas. Check that the selected completion model supports output schemas, and smoke-test advanced Zod features on the exact provider path.
Local Zod parsing remains the final boundary even when the provider accepts the schema.