wellformed

Comparison

When to pick wellformed instead of JSON Schema, Zod, Yup, or Joi.

Use wellformed when validation has to leave JavaScript.

Zod is great inside one TypeScript app. JSON Schema is great when ecosystem compatibility matters most. wellformed is for the middle: Zod-like authoring, portable JSON rules, and the same validation behavior in TypeScript and Rust.

The shortest version

If your schema needs to be stored, inspected, sent over the wire, or evaluated outside JavaScript, use wellformed.

TypeScript builder
      |
      v
Serializable JSON IR
      |
      +--> TypeScript validate()
      |
      +--> Rust validate()

Pick The Tool

If you need...Use
Form or API validation inside one TypeScript app, nothing leaving JSZod
OpenAPI compatibility and broad ecosystem toolingJSON Schema
Shared TypeScript and Rust validationwellformed
Validation rules stored in a database or config servicewellformed
SSN, EIN, IBAN, routing number, CUSIP, ICD-10, and similar predicateswellformed
Cross-field rules that remain inspectable datawellformed
Predicates, transforms, and cross-field checks without custom glue codewellformed
A legacy Node validation libraryYup / Joi

A note on speed

Speed is not a reason to choose Zod over wellformed. In the same V8 runtime, wellformed-ts validates 10 to 40x faster than Zod, and on par with Valibot. The Rust runtime is faster still. See the benchmark.

Why Developers Switch

Rules are data

Schemas compile to JSON. Store them, diff them, sign them, and move them across services.

One schema, two runtimes

Author in TypeScript. Validate in TypeScript or Rust without rewriting the rules.

Domain validators included

Built-ins cover tax, finance, healthcare, IDs, URLs, dates, phones, and more.

Less custom logic

Common predicates, transforms, and cross-field checks are schema methods, not one-off helper functions.

Logic stays inspectable

Transforms, predicates, error codes, and cross-field rules stay visible in the IR.

At A Glance

CapabilitywellformedJSON SchemaZodYup / Joi
TypeScript builderYesNoYesYes
Type inferenceYesNoYesLimited
Serializable schemaYesYesNoNo
TypeScript runtimeYesYesYesYes
Rust runtimeYesVia other validatorsNoNo
First-class transformsYesNoJS closuresJS callbacks
Inspectable cross-field rulesYesAwkwardNoLimited
Domain predicates60+ built-insFormat onlyMinimalMinimal
Custom code needed for rich form rulesLowHighMediumMedium
Best fitPortable validationStandards and OpenAPITS-only appsLegacy apps

vs JSON Schema

wellformed is inspired by JSON Schema's best idea: schemas should be portable data. It is not a JSON Schema implementation or dialect.

JSON Schema wins when you need OpenAPI, editor integration, and validators in many languages.

wellformed wins when you need behavior JSON Schema does not model cleanly:

  • transforms before validation, like trim() and digitsOnly()
  • domain predicates like ssn(), ein(), iban(), and abaRouting()
  • TypeScript inference from the authoring schema
  • cross-field business rules without deeply nested allOf / if / then
// wellformed: the rule is compact and serializable
w.object({
  type: w.enum(["individual", "business"] as const),
  ssn: w.string().ssn().optional(),
  ein: w.string().ein().optional(),
})
  .when("type").equals("individual").require("ssn")
  .when("type").equals("business").require("ein");

vs Zod

Zod is excellent when everything runs in JavaScript.

The problem starts when the schema needs to cross a runtime boundary. Zod schemas are JavaScript objects with closures. They cannot be serialized into a portable validation program.

wellformed also cuts down the amount of custom validation code you write for forms with normalization, domain predicates, and business rules:

// Zod: JS-only transform
z.string().transform((value) => value.replace(/\D/g, ""));

// wellformed: serializable transform, also implemented in Rust
w.string().digitsOnly();
// wellformed: transform + predicate + cross-field rule, still serializable
w.object({
  kind: w.enum(["individual", "business"] as const),
  ssn: w.string().digitsOnly().ssn().optional(),
  ein: w.string().digitsOnly().ein().optional(),
})
  .when("kind").equals("individual").require("ssn")
  .when("kind").equals("business").require("ein");

Use Zod if you want maximum TypeScript ecosystem compatibility and do not need serialization.

Use wellformed if the same rule has to run in a browser, a Node service, and a Rust service without becoming three separate implementations.

vs Yup / Joi

Yup and Joi are mature Node validation libraries. They work well in legacy codebases, but they do not solve the portability problem:

  • schemas are runtime objects, not portable JSON
  • TypeScript inference is weaker than wellformed or Zod
  • cross-field rules are harder to inspect
  • domain predicates usually become custom application code

Where wellformed Is Not The Right Fit

  • You need full JSON Schema or OpenAPI compatibility.
  • You need arbitrary JavaScript transforms inside the schema.
  • You only validate in one TypeScript app and already like Zod.
  • You need validators in many languages today, not TypeScript and Rust.

Next

On this page