Validation logic, as data.
Write the schema once. It compiles to JSON, then validates identically in JavaScript, TypeScript, and Rust: same transforms, same rules, same error codes. One source of truth, no drift across runtimes.
MIT · TS & Rust · 60+ validators
import { w, validate, type Infer } from "wellformed-ts";
// Domain validators + cross-field rules, all built in.
const Account = w.object({
email: w.string().email(),
ssn: w.string().ssn().optional(),
ein: w.string().ein().optional(),
type: w.enum(["individual", "business"] as const),
})
.when("type").equals("individual").require("ssn")
.when("type").equals("business").require("ein");
type Account = Infer<typeof Account>;
// Compile to portable IR, then validate.
const result = validate(Account.toSchema("1.0"), input);Where the rules travel
Your form isn't always a web page.
Often the form isn't fixed in your code. It's built in a form builder, configured per customer, or one of hundreds of forms that each carry their own rules. And whatever renders or receives it, the browser, the backend, a Rust service, needs those same rules.
Most validation libraries write those rules as code, and code is not data. You can't store a closure in a database, load the rules for one customer's form, or hand them to a Rust service. So every form gets hardcoded and every rule rewritten by hand, and the copies drift out of sync.
wellformed compiles the rules to JSON. Store them with each form, load them at runtime, and run them unchanged in TypeScript and Rust.
Form builders
Users and tenants define fields at runtime. The rules live with the form, not hardcoded in your app.
Web & mobile
Inline field errors as the user types, straight from the stored schema.
PDFs & documents
Validate a filled document's fields before you accept or render it.
Any backend
Validate submitted JSON in Node, Rust, or a worker before you trust the shape.
AI agents
Generate and inspect rules as data, not opaque closures.
One schema · five surfaces · zero reimplementation
Why wellformedHow it works
Author once. Compile to data. Run anywhere.
Write the schema with a Zod-like builder. wellformed compiles it to a JSON IR: a complete program of predicates, transforms, error codes, and cross-field rules, with nothing hidden in closures.
Store that IR, send it over the wire, or load it by id. The TypeScript and Rust runtimes read the same file and validate identically.
wellformed-ts
wellformed
Try it live
See validation as data, live.
Edit a schema's IR on the left. A real form validates on the right: predicates, transforms, cross-field rules, error codes, all in your browser. Seeing the wire format do the work converts skeptics faster than any pitch.
Open the Playgroundrequired when type is business
Batteries included
Everything you'd otherwise re-implement on both sides.
Portable IR
schemaToJSON(schema)
Author in TypeScript, ship as JSON. Store it, diff it in code review, version it like any other artifact.
TypeScript + Rust
validate(&schema, &input)
One schema, two first-class native runtimes. The same rules produce the same results on both sides of the wire.
60+ domain validators
is_ssn · is_ein · is_cusip · is_icd10
TINs, financial identifiers, healthcare and aviation codes, contact fields. Built in. No regex archaeology.
Cross-field rules
.when("type").equals("business")
Conditional requirements, mutual exclusion, field comparisons. Real business logic, expressed as data.
Transform pipeline
trim · digits_only · money→cents
Normalize before you validate. Transforms travel inside the schema, so every runtime cleans data identically.
Full type inference
type T = Infer<typeof schema>
Exact TypeScript types straight from the builder. Optional-aware, enum-aware, zero duplicate definitions.
Performance
Purpose-built beats regex.
Most validators lean on generic regex. wellformed's built-in predicates are purpose-built, so an email or SSN checks in tens of nanoseconds instead of microseconds. That is 10 to 40x faster than Zod in the same V8 runtime, and the Rust runtime goes further with SIMD-friendly byte scanners.
Validation latency, lower is faster. Same V8 runtime, Apple Silicon.
Stop maintaining two copies of the truth.
Define your validation once. Ship it as data. Run it everywhere.