The Validation Sprawl Problem

Zod is one of the best things to happen to the TypeScript ecosystem. It brought type-safe runtime validation to JavaScript with an elegant, chainable API. We genuinely admire it. But when you use Zod in a real application, you quickly discover that validation is not a single problem — it is a constellation of related problems, and Zod solves only one of them.

Here is what validation looks like in a typical production application:

That is three to four different validation contexts, often using slightly different schemas, sometimes different libraries, with no guarantee they stay in sync. When the business rule changes — say, minimum password length goes from 8 to 12 — you have to update it in multiple places and hope you did not miss one.

forge/schema: One Schema, Everywhere

forge/schema provides a Zod-compatible API (if you know Zod, you already know forge/schema) with crucial additions that make it a complete validation solution across your entire stack:

import { z } from 'hbforge/schema'; // Define once — use on client AND server const UserSchema = z.object({ name: z.string() .min(2, 'Name must be at least 2 characters') .max(100), email: z.string() .email({ tldCheck: true }), // Rejects user@fake, user@localhost password: z.string() .min(12) .regex(/[A-Z]/, 'Must contain uppercase') .regex(/[0-9]/, 'Must contain a number'), role: z.enum(['admin', 'editor', 'viewer']), age: z.number() .int() .min(18) .max(120), }); // Type inference works identically to Zod type User = z.infer<typeof UserSchema>;

This same schema object works on the client in a form, on the server in a route handler, and anywhere else you need validation. Change the minimum password length in one place, and it updates everywhere.

TLD-Aware Email Validation

This might seem like a minor detail, but it is one of the most common sources of bad data in production systems. Zod's .email() validates email syntax according to the RFC spec, which means user@localhost, test@fake, and admin@company all pass validation. They are syntactically valid emails. They are also completely useless.

forge/schema's email validator includes an embedded TLD database (all IANA-registered TLDs) and validates that the domain portion of the email address has a real top-level domain:

import { z } from 'hbforge/schema'; const schema = z.string().email({ tldCheck: true }); schema.parse('user@gmail.com'); // OK — .com is a real TLD schema.parse('user@company.co.uk'); // OK — .uk is a real TLD schema.parse('user@startup.io'); // OK — .io is a real TLD schema.parse('user@localhost'); // FAIL — no TLD schema.parse('user@test.fake'); // FAIL — .fake is not a real TLD schema.parse('user@company'); // FAIL — no TLD // Still RFC-compliant syntax checking underneath. // TLD check is opt-in, so you can disable it for internal tools.

This single feature eliminates an entire class of bad signups, broken password resets, and undeliverable notification emails. Most teams either do not validate TLDs at all (letting garbage data into their database) or implement a custom regex that quickly becomes outdated as new TLDs are registered.

Deep Form Integration

The biggest gap in the Zod ecosystem is the connection between validation and forms. You need @hookform/resolvers to bridge Zod to React Hook Form, which itself is a workaround for React's slow controlled inputs. It is adapters all the way down.

forge/schema integrates directly with forge/client's form system. No adapters. No resolvers. No bridge packages:

import { createForm } from 'hbforge/client'; import { z } from 'hbforge/schema'; const RegisterSchema = z.object({ email: z.string().email({ tldCheck: true }), password: z.string().min(12), confirmPassword: z.string(), }).refine( (data) => data.password === data.confirmPassword, { message: 'Passwords must match', path: ['confirmPassword'] } ); const form = createForm({ schema: RegisterSchema, validateOn: 'blur', // validate when field loses focus revalidateOn: 'input', // re-validate on every keystroke after first error onSubmit: async (values) => { await api.register(values); }, }); // form.fields.email.value — reactive signal // form.fields.email.error — reactive signal (null or error string) // form.fields.email.touched — reactive signal // form.isValid — computed from all fields // form.isDirty — computed, true if any field changed // form.errors — computed, all current errors

Every field is a signal. Validation runs through the same schema on the client that runs on the server. The form state is reactive without re-rendering the entire component. Error messages are defined in the schema, not in the form component, so they stay consistent across your application.

Domain-Specific Validators

Zod is intentionally generic. It validates data types. But real applications need to validate domain concepts — credit card numbers, phone numbers with country codes, URLs with protocol requirements, dates within business-logic ranges, currency amounts with precision constraints.

forge/schema includes domain-aware validators that go beyond type checking:

import { z } from 'hbforge/schema'; // URL with protocol enforcement z.string().url({ protocols: ['https'] }) // ISO date strings with range z.string().datetime().after('2020-01-01').before('2030-12-31') // Slug format (lowercase, hyphens, no spaces) z.string().slug() // Semantic versioning z.string().semver() // Hex colors z.string().hexColor() // CUID, UUID, ULID — all built-in z.string().cuid() z.string().uuid() z.string().ulid() // JSON string validation z.string().json() // IP addresses with version z.string().ip({ version: 'v4' })

Each of these validators is purpose-built, not a regex wrapper. The email validator understands TLDs. The URL validator understands protocol semantics. The datetime validator understands ISO 8601 nuances. These are not string pattern matches — they are parsers that understand the structure of the data they validate.

Server-Side Integration

On the server, forge/schema integrates with forge/server's route handlers to provide automatic request validation:

import { ForgeServer } from 'hbforge/server'; import { z } from 'hbforge/schema'; const CreateUserBody = z.object({ name: z.string().min(2), email: z.string().email({ tldCheck: true }), role: z.enum(['admin', 'editor', 'viewer']), }); app.post('/api/users', { body: CreateUserBody, // Auto-validates request body async handler(ctx) { // ctx.body is already validated and typed // If validation fails, 400 response is sent automatically // with structured error messages matching the schema const user = await db.users.create(ctx.body); return ctx.json(user, 201); }, });

The schema validates the request body automatically. If validation fails, the server returns a structured 400 response with field-level error messages — the same error messages your client form would show. No manual error handling. No try/catch around schema.parse(). The framework handles it.

Why Not Just Use Zod?

You can. forge/schema's API is deliberately Zod-compatible, so migrating is straightforward. But here is what you gain with forge/schema that Zod alone cannot provide:

"Validation is not a library problem. It is a system problem. The validator needs to understand the form, the server, and the domain."

Validation That Understands Your Stack

Zod-compatible API. TLD-aware emails. Direct form and server integration. One schema definition for your entire application.

Read the Docs Request Early Access

HBForge is currently available exclusively for enterprise clients. Opening to the Developer Community on June 25, 2026. Contact kr@hyperbridge.in for early access.