Build apps with Zod
Zod provides runtime type validation for forms, API responses, and generated data structures throughout every Goodspeed app. Goodspeed generates Zod as a standard part of every app, so the output is a working codebase from day one, not a scaffold you have to finish yourself.
WHAT GETS GENERATED
Built into every Zod build
Every app Goodspeed generates with Zod includes these production-ready patterns, wired together from the first build.
| Item | Description | Strength |
|---|---|---|
| lib/validation.ts common schemas | emailSchema, passwordSchema, displayNameSchema, and feedbackSchema export reusable Zod validators. The DevAgent adds app-specific schemas for every form in the generated app. | Schemas |
| validate() typed helper | A generic validate<T>(schema, data) helper returns { success: true; data: T } or { success: false; errors: Record<string, string> }, so no try/catch at call sites. | API |
| react-hook-form integration via zodV4Resolver | lib/forms.ts includes a zodV4Resolver wrapper compatible with @hookform/resolvers that normalizes Zod v4 issues to the shape RHF expects. | Forms |
| Supabase response validation | Edge Function responses and Supabase RPC calls are parsed through Zod schemas before the data reaches the component, catching malformed responses before they crash the UI. | API Safety |
Source: the Goodspeed generation pipeline · Schema Validation
REAL GENERATED CODE
A snippet from a Zod app the studio shipped
This pattern comes straight out of the production groundwork every Goodspeed app is generated on. The studio generates Zod code like this for every app in the pipeline, not a hello-world sample you have to grow into a real app.
validate helper
// lib/validation.ts: typed validation helper type ValidationResult<T> = | { success: true; data: T } | { success: false; errors: Record<string, string> }; export function validate<T>( schema: ZodSchema<T>, data: unknown ): ValidationResult<T> { const result = schema.safeParse(data); if (result.success) return { success: true, data: result.data }; const errors: Record<string, string> = {}; result.error.issues.forEach((e) => { const key = e.path.join('.') || 'root'; errors[key] = e.message; }); return { success: false, errors }; }
Today's log