Home → Blog → Runtime Validator Comparison
JSON Schema vs Zod vs TypeBox vs Yup vs Joi — Runtime Validator Comparison (2026)
Choosing a runtime validator for a TypeScript or JavaScript project in 2026 used to be a question of "Joi or Yup?" — both libraries that predate widespread TypeScript adoption. The landscape now includes JSON Schema (via Ajv), Zod, TypeBox, Valibot, and ArkType, each with different trade-offs around type inference quality, runtime performance, ecosystem fit, and whether the schema doubles as a public contract. This article compares the five most-deployed options — JSON Schema, Zod, TypeBox, Yup, and Joi — across the dimensions that matter in production: developer experience, performance, type inference, error messages, ecosystem, and bundle size. The recommendation at the end is opinionated.
Generate validators from a JSON sample
Paste a JSON example, get back a Zod schema, TypeScript type, or JSON Schema you can drop into your validator of choice.
Open JSON to Zod →The Comparison Matrix
| Dimension | JSON Schema (Ajv) | Zod | TypeBox | Yup | Joi |
|---|---|---|---|---|---|
| Type inference (TS) | Indirect (via json-schema-to-ts) | Native, excellent | Native, excellent | Good, some gaps | Limited |
| Runtime performance | Fastest (compiled) | Medium | Fast (compiles to JSON Schema) | Slower | Slower |
| Bundle size | ~30 KB minified | ~12 KB minified | ~3 KB minified | ~30 KB | ~150 KB (server-only) |
| Schema as cross-language contract | Yes (canonical) | No | Yes (compiles to JSON Schema) | No | No |
| Error message quality | Detailed, technical | Good, customizable | Inherits from Ajv | Good | Best out-of-box |
| Discriminated unions | oneOf + const | z.discriminatedUnion (excellent) | Type.Union with literal | Limited | alternatives() + try |
| Async validation | Yes | z.string().refine async | No (sync only) | Yes | Yes |
| Coercion (string → number) | No | z.coerce.number() | No | Yes | Yes |
| Form library integration | Indirect | react-hook-form, formik via @hookform/resolvers | react-hook-form via resolver | Formik native | react-hook-form via resolver |
| Maintained / active | Yes (Ajv, very active) | Yes (very active) | Yes (active) | Yes (slower pace) | Yes (slower pace) |
JSON Schema (via Ajv)
JSON Schema is the only option in this list that is a published standard rather than a library API. A schema is a JSON document; any conforming validator can use it. Ajv is the dominant JavaScript implementation.
import Ajv from "ajv";
const ajv = new Ajv();
const schema = {
type: "object",
properties: {
id: { type: "integer", minimum: 1 },
email: { type: "string", format: "email" },
role: { enum: ["admin", "user"] }
},
required: ["id", "email", "role"],
additionalProperties: false
};
const validate = ajv.compile(schema);
if (!validate(data)) console.log(validate.errors);
Pick JSON Schema when: you publish an API consumed by clients in multiple languages (Python, Go, Java, etc.); you generate OpenAPI documentation; you need the absolute fastest runtime validation; you want an open standard rather than a library lock-in.
Trade-offs: raw JSON Schema is verbose and awkward to write by hand. TypeScript inference requires a separate library (json-schema-to-ts) and works best when schemas are declared as const. Error messages are technical — fine for logs, often need post-processing for users.
Zod
Zod has become the default runtime validator for new TypeScript projects. The killer feature is identical syntax for runtime validation and TypeScript types — you write the schema once, and z.infer<typeof Schema> gives you the type.
import { z } from "zod";
const User = z.object({
id: z.number().int().positive(),
email: z.string().email(),
role: z.enum(["admin", "user"]),
});
type User = z.infer<typeof User>; // { id: number; email: string; role: "admin" | "user" }
const result = User.safeParse(data);
if (!result.success) console.log(result.error.issues);
Pick Zod when: you're writing a TypeScript application; you want one source of truth for types and runtime validation; you use tRPC, Astro form actions, or SvelteKit; you want excellent discriminated union support; you're starting a new project in 2026.
Trade-offs: not as fast as Ajv (still plenty fast for most apps); schemas are not portable to non-JS consumers; the runtime parser is more verbose than the equivalent JSON Schema for simple cases.
Zod's killer feature: z.discriminatedUnion
const Event = z.discriminatedUnion("type", [
z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
z.object({ type: z.literal("submit"), formId: z.string() }),
]);
// Automatically narrows the type when you check event.type
TypeBox
TypeBox is the lesser-known option that often deserves more attention. It compiles schemas to JSON Schema at runtime, then validates with Ajv — combining Zod-like ergonomics with Ajv-class performance and standard-format output.
import { Type, Static } from "@sinclair/typebox";
const User = Type.Object({
id: Type.Integer({ minimum: 1 }),
email: Type.String({ format: "email" }),
role: Type.Union([Type.Literal("admin"), Type.Literal("user")]),
});
type User = Static<typeof User>;
// User is also valid JSON Schema you can publish to OpenAPI
Pick TypeBox when: you want Zod-like DX and publishable JSON Schema output; you care about validation performance; you're using Fastify (which uses TypeBox internally for type inference); your bundle size budget is tight (3 KB vs Zod's 12 KB).
Trade-offs: smaller ecosystem than Zod; no async refinement; some advanced patterns (transformations, complex coercion) are harder to express; less prior art when you Google a problem.
Yup
Yup predates the TypeScript-first wave. It is most commonly seen with Formik for form validation. Recent versions have improved TypeScript support but still have rough edges around discriminated unions and complex transforms.
import * as yup from "yup";
const userSchema = yup.object({
id: yup.number().integer().positive().required(),
email: yup.string().email().required(),
role: yup.string().oneOf(["admin", "user"]).required(),
});
type User = yup.InferType<typeof userSchema>;
Pick Yup when: you're using Formik (it integrates natively); you have an existing Yup codebase and migration cost is high; you need a battle-tested, conservative library.
Trade-offs: TypeScript inference has gaps; discriminated unions are awkward; slower than Zod and significantly slower than Ajv; for new projects in 2026, Zod is usually the better default even if it costs you the Formik integration.
Joi
Joi is the elder statesman — created by the hapi.js team, originally server-only. It has the most human-friendly error messages and the most mature feature set, but TypeScript support is the weakest of the five and the bundle is large enough that it is essentially unusable in the browser.
import Joi from "joi";
const userSchema = Joi.object({
id: Joi.number().integer().positive().required(),
email: Joi.string().email().required(),
role: Joi.string().valid("admin", "user").required(),
});
const { error, value } = userSchema.validate(data);
Pick Joi when: you're on a Node.js server with no client validation needs; you want the best default error messages; you're using hapi.js; you have an existing Joi codebase.
Trade-offs: ~150 KB bundle (server-only realistically); TypeScript inference is limited; significantly slower than Ajv. For new projects, Joi is rarely the right default.
Performance Reality Check
Validation is rarely a bottleneck for most applications. Even the slowest of these libraries can validate thousands of objects per second on modern hardware. The order of magnitude differences (Ajv being 10–50x faster than Joi) only matter in specific scenarios:
- High-throughput APIs validating every request body (10,000+ req/sec)
- Bulk import / ETL pipelines processing millions of records
- Real-time validation in WebSocket message handlers
- Edge runtimes (Cloudflare Workers, Deno Deploy) with strict CPU budgets
For 95% of applications — form validation, occasional API checks, configuration parsing — the validator's developer experience matters far more than its raw speed.
Type Inference Quality
This is where Zod and TypeBox decisively win. Both produce a TypeScript type from a single source of truth. JSON Schema requires a third-party tool, Yup has gaps, and Joi's inference is limited.
| Pattern | JSON Schema | Zod | TypeBox | Yup | Joi |
|---|---|---|---|---|---|
| Object with required fields | OK (with helper) | Excellent | Excellent | Good | Limited |
| Discriminated union | OK | Excellent (auto-narrowing) | OK | Difficult | Difficult |
| Optional vs nullable | OK | Excellent | OK | Confusing | Limited |
| Recursive schemas | Possible | z.lazy() | This.Recursive | Difficult | Limited |
| Branded types (e.g. UserId) | No | z.brand() | Yes | No | No |
Ecosystem and Form Library Fit
This often decides the call:
- react-hook-form: works with all five via
@hookform/resolvers(Zod, Yup, Joi, TypeBox, JSON Schema all supported) - Formik: native Yup integration; Zod via third-party
- tRPC: Zod is the canonical choice; uses Zod schemas for input validation
- Astro / SvelteKit form actions: Zod-first ecosystems
- Fastify: TypeBox is the canonical choice; TypeBox schemas drive both validation and type inference for routes
- NestJS: class-validator (decorator-based) is the default, but Zod and Joi are common alternatives
- Express / Hono: framework-agnostic; pick based on team preference
Decision Tree
- Is this a TypeScript project that will live on the JS runtime exclusively? → Zod (default) or TypeBox (if performance/portability matter).
- Do you publish an API consumed by clients in multiple languages? → JSON Schema (via Ajv) or TypeBox (which compiles to JSON Schema).
- Are you using Formik? → Yup (native integration) unless migrating.
- Are you on hapi.js or have a large Joi codebase? → Stay on Joi; migration cost rarely worth it.
- Edge runtime or strict bundle budget? → TypeBox (3 KB) or Valibot (1.7 KB; not covered above but worth investigating).
- Need maximum throughput? → JSON Schema with Ajv compiled.
The Honest Recommendation for 2026
- Default for new TypeScript projects: Zod. The DX is excellent, the ecosystem is huge, and 95% of applications won't notice the performance gap to Ajv.
- If you also publish an OpenAPI spec or cross-language contract: TypeBox. Same DX, schemas double as JSON Schema.
- If validation is on the hot path of a high-RPS server: JSON Schema with Ajv (compiled mode). Pair with
json-schema-to-tsfor type inference. - If you're already on Yup or Joi and it works: don't migrate without a concrete reason.
Generating Schemas from JSON Samples
Whichever validator you choose, the fastest way to bootstrap a schema is to start from a JSON sample and let a tool emit the validator code. Use these for rapid iteration:
- JSON to Zod — generate Zod schemas from any JSON
- JSON to TypeScript — TypeScript type definitions
- JSON Schema Generator — generate raw JSON Schema
- JSON to Mongoose — Mongoose schemas (for MongoDB)
- JSON to Prisma — Prisma model definitions
Generate a Zod schema from a JSON sample
Paste a JSON object and get a ready-to-use Zod schema with proper TypeScript inference. Free, runs in your browser.
Open JSON to Zod →Frequently Asked Questions
On most workloads yes — Zod is generally several times faster than Joi at validation. However, Ajv (a JSON Schema validator) is typically faster than both Zod and Joi by a wide margin because it compiles schemas to specialized validators. Pick a validator based on developer experience first; if validation is a measured bottleneck, switch to Ajv.
Zod has the better developer ergonomics and broader ecosystem. TypeBox is faster at runtime and produces real JSON Schema you can publish externally. Pick Zod for application code; TypeBox if you need to publish schemas externally or care about validation performance.
JSON Schema is a declarative, language-agnostic format defined by an IETF spec — schemas are JSON documents. Zod is a TypeScript-first builder API where you compose schemas as code and the library validates and infers types from the same definition.
Yup is still maintained and widely used in form validation contexts (especially with Formik), but newer projects increasingly prefer Zod for its TypeScript inference. If you're starting a new project today, Zod is the more common choice.
Out of the box, Joi has the most human-readable default error messages. Zod's are good and easily customizable per-field. Ajv (JSON Schema) errors are technically detailed but require post-processing for end-user display.