Twenty One Media
webMay 21, 2026

One File Owns the Form: Zod as a Single Source of Truth

The Operations Engine audit form collects four types of qualifying data: the service categories a business wants to focus on (quote pipeline, office automations, job history memory, operations audit), a timeline, a budget range, and a free-text description of their operational pain. Three of those four are enum selects where the allowed values have to be validated on the server and displayed as readable strings in notification emails.

The obvious approach is to split this across files. The UI component holds the option arrays, the validation schema holds the allowed slugs, and a utility somewhere maps slugs to display strings. When you add a new service category, you touch three files. If you miss one, you get either a TypeScript error or a silent runtime mismatch.

We put all three concerns in one file.

What Goes in the Schema File

src/lib/validation/operations-engine-lead.ts exports everything that involves the form's constrained options:

  • SERVICE_OPTIONS: an as const tuple of the allowed service slugs
  • operationsEngineLeadSchema: a Zod schema validating the full POST payload
  • OperationsEngineLeadData: a TypeScript type inferred from the schema via z.infer
  • labelService, labelTimeline, labelBudget: functions that convert slugs to display strings

The key coupling is in the label functions. labelService takes (typeof SERVICE_OPTIONS)[number] as its argument type, and the internal SERVICE_LABELS record uses that same type as its key. If you add a new slug to SERVICE_OPTIONS but forget to add it to SERVICE_LABELS, TypeScript raises an error on the record definition: the key type no longer covers all enum members. You cannot ship a form option without a label.

How It Flows

The AuditForm client component imports SERVICE_OPTIONS directly to render the checkbox group. It doesn't define the options itself. The server route imports operationsEngineLeadSchema for validation and the label functions for email formatting:

const labeledServices = services.map(labelService).join(", ");

The notification email that arrives when someone submits an audit request reads "Quote & Lead Pipeline, Office Automations" instead of "quote-pipeline,office-automations". That formatting decision lives in the same file that defined the allowed values. Changing how a service is labeled means changing one string in one place.

Why This Helps on Qualifying Forms

An audit request form is different from a simple email capture. The person on the other end of the notification email, in this case us, needs to read it and immediately understand what the prospect wants, their timeline, and their budget. The notification email is a pre-call brief. Readable values matter.

Keeping options and labels colocated also makes auditing easier. If we want to know every place the "quote-pipeline" value is used, we grep the one schema file. Imports tell us who consumes it. There's no hunting across components and utility files to understand the full picture.

We use the same pattern for BUDGET_OPTIONS and TIMELINE_OPTIONS in the same file. Budget has four tiers from "Under $5,000" to "$30,000+". Timeline has four slots from "This month" to "Just exploring." Each has a corresponding label function. Each label function is typed against its option set, so completeness is enforced at compile time.

This is not a library or a pattern from a framework. It is just as const, z.infer, and putting related things in one place. The TypeScript types, the Zod schema, and the display strings stay together because they describe the same domain concept.