- Published on
TypeScript Data Validation 2026 — Zod·Valibot·ArkType·TypeBox·Effect Schema Deep Comparison (Runtime Validation in the AI Tool-Calling Era)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
Prologue — The era when every API boundary demands a schema
In 2022, "what should we use for TypeScript runtime validation?" had one answer: Zod. Before that, Joi, Yup, or hand-written type guards.
In 2026 the same question has five candidates. A lot happened in between.
- tRPC became standard. Putting a Zod schema on every procedure input became the default idiom of full-stack TS. Schema = API boundary.
- React Hook Form + zodResolver became the de-facto form pattern. Form validation = the same schema.
- AI tool calling (function calling, tool use) mandates JSON Schema. OpenAI, Anthropic, and Gemini all accept tool definitions as JSON Schema. If your TS schema can't export JSON Schema, you're in trouble.
- OpenAPI auto-generation is hot again. Hono, tRPC-openapi,
@asteasolutions/zod-to-openapi, TypeBox's built-in interop. One schema, three artifacts: client, server, docs. - Bundle size = money. More code runs at the edge — Cloudflare Workers, Vercel Edge. The 1MB-Zod era is gone, but a camp now counts every KB.
Five camps emerged.
- Zod 4 — The de-facto standard. Version 4 hit the two old weaknesses directly: tree-shaking and parser speed.
- Valibot — Functional, modular API. Only what you use ends up in the bundle. Wins frontend forms and edge.
- ArkType — Uses TS strings as schema.
type({ email: 'string.email' }). 100% TS-inference alignment. - TypeBox — Schemas you write are JSON Schema. Direct line to Fastify, OpenAPI, AI tool calling.
- Effect Schema — Effect ecosystem's validator. Typed errors, composable transforms, bidirectional encoding.
- Yup / Joi / Superstruct — Alive but new adoption keeps shrinking.
This post compares these libraries head-to-head as of May 2026. Seven axes: bundle, inference, JSON Schema, async, OpenAPI, runtime cost, ecosystem. Plus the same User schema in all five.
1. Landscape — Map of data validation libraries
Classification first. Not everything sits in the same place.
| Category | Representative | One-line summary |
|---|---|---|
| Object builder (chaining) | Zod, Yup, Joi | z.string().email() style. Most familiar. |
| Modular functions | Valibot | string([email()]). Tree-shake friendly. |
| TS syntax as schema | ArkType | Use TS type expressions at runtime. |
| JSON Schema native | TypeBox | What you write is JSON Schema. |
| Functional + Effect | Effect Schema | Typed errors, bidirectional encoding. |
| Lightweight assertion | Superstruct, io-ts | Small, composable, slightly older style. |
This post focuses on the five bold splits — Zod, Valibot, ArkType, TypeBox, Effect Schema. Yup, Joi, and Superstruct get a short chapter near the end.
Why these five
- Zod 4 — Default for new TypeScript projects. Appears in tRPC, Next.js, React Hook Form everywhere. Zod 4 announced in 2024 and stabilized in 2025–2026 erased the tree-shaking weakness.
- Valibot — Took off in 2024. Function-level imports let unused code drop out of the bundle. Rapidly gained share in frontend and edge.
- ArkType — A worldview shift: accept TS expressions directly. New projects after v2 stabilization.
- TypeBox — Recommended by Fastify; paired with AJV it's one of the fastest runtimes. First pick when LLMs need raw JSON Schema for tool calling.
- Effect Schema — The validator if your team goes all-in on Effect. Hard to justify otherwise.
2. Seven-axis comparison matrix
Before the deep dives, here's the big picture.
| Axis | Zod 4 | Valibot | ArkType | TypeBox | Effect Schema |
|---|---|---|---|---|---|
| Bundle (core) | Medium | Very small (modular) | Small | Very small | Large (Effect core included) |
| TS inference quality | Very good | Very good | Best (TS expressions as-is) | Good | Very good |
| JSON Schema export | External pkg | External pkg | Built-in | Built-in (definition IS JSON Schema) | Built-in |
| Async validation | Yes | Yes | Partial | No (sync-focused) | Yes |
| OpenAPI export | zod-to-openapi | @valibot/to-json-schema | Built-in | Built-in (Fastify/AJV) | @effect/schema-openapi |
| Runtime cost | Fast (large gain in v4) | Very fast | Fast | Fastest (when AJV-compiled) | Medium–fast |
| Ecosystem | Largest | Growing fast | Medium | API/Fastify camp | Effect users |
Don't decide from this table alone. Next chapters pin down what each does and doesn't do.
3. Zod — Why it became the standard, and Zod 4's comeback
Since landing in 2020, Zod has been the de-facto TypeScript validation standard. Three reasons.
- Chaining API familiarity.
z.string().email().min(5)reads as intent. Near-zero learning cost for anyone coming from Joi or Yup. z.infertype inference. Derive TS types directly from the schema. Schema = single source of truth for the type.- Ecosystem explosion. tRPC,
@hookform/resolvers, OpenAI SDK, LangChain, Next.js Server Actions — all accept Zod first-class.
Zod 3-era weaknesses
Two criticisms followed Zod for years.
- Bundle size. Because the chaining API pulls in the whole index together, tree-shaking was weak. Even a bare
z.string()dragged in tens of KB. - Runtime cost. Parsing deeply nested objects was surprisingly heavy. Hot paths (thousands of calls per request) felt it.
What Zod 4 changed
Zod 4 beta arrived October 2024; GA followed in 2025. Three big changes.
- Tree-shakeable build. Unused validators drop out of the bundle. You still write
import { z } from 'zod', but the resulting bundle is smaller. - Parser rewrite. Simplified internals and optimized hot paths. Official benchmarks show meaningful gains over v3 (consult release notes for numbers).
- Split builds. A core
zodandzod/v4, plus a mini build (zod/v4-mini) targeting Valibot-class bundles.
Same User schema in Zod
import { z } from 'zod'
const User = z.object({
email: z.string().email(),
age: z.number().int().min(0).max(150),
name: z.string().min(1),
})
type User = z.infer<typeof User>
const parsed = User.parse({ email: 'foo@example.com', age: 30, name: 'Foo' })
// ^ parsed: User
safeParse for errors as values:
const result = User.safeParse(unknown)
if (!result.success) {
// result.error is a ZodError; pretty issues at result.error.issues
console.error(result.error.flatten())
}
Zod strengths (2026)
- Largest ecosystem. New libraries and SDKs almost auto-ship a Zod adapter.
- Bundle and speed weaknesses largely closed in v4.
- Async validation (
z.string().refine(async ...)) is smooth. - Learning material dominates.
Zod weaknesses (2026)
- Absolute bundle still bigger than Valibot. Not a candidate if you fight for KB on edge workers.
- Some APIs like
z.discriminatedUnioncarry a slight learning cost. - JSON Schema export needs an external package (
@asteasolutions/zod-to-openapietc.). Native support is partial.
4. Valibot — Shrinking the bundle with functional modules
Valibot exploded between late 2023 and 2024. Core philosophy:
"Write the basics as functions. What you don't use isn't in the bundle."
Where Zod is z.string().email().min(5), Valibot is function composition.
import * as v from 'valibot'
const User = v.object({
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
name: v.pipe(v.string(), v.minLength(1)),
})
type User = v.InferOutput<typeof User>
const parsed = v.parse(User, { email: 'foo@example.com', age: 30, name: 'Foo' })
Compose validators with v.pipe(...), then apply with v.parse/v.safeParse. Functions are all named exports — esbuild/Rollup drops unused ones.
Bundle gap
The picture from the official site and community benchmarks:
- Using only
v.string()once in Valibot pulls in a few hundred bytes. - The same code in Zod 3 pulled in tens of KB. Zod 4 closes that gap a lot, but Valibot is still smaller.
- With a big schema (many validators), the gap narrows. The smaller you start, the more Valibot wins.
Valibot's BaseSchema API
Stable since Valibot 1.0 GA in 2024. Main functions:
v.object,v.array,v.tuple,v.union,v.intersect,v.recordv.string,v.number,v.boolean,v.literal,v.date,v.bigintv.pipe(schema, ...actions)— validator compositionv.transform(fn),v.brand(name)— transforms and brand types
Async uses v.parseAsync/v.safeParseAsync.
Valibot strengths
- Bundle leader — smallest in frontend and edge.
- Inference quality — both
InferOutputandInferInputare precise. - Functional means easy composition — low cost to write custom validators.
- Fast-growing ecosystem — Standard Schema support,
@valibot/to-json-schema,@hookform/resolvers/valibot.
Valibot weaknesses
- API learning curve — initially awkward if you're used to chaining.
- Custom error messages take slightly more work.
- Some libraries that ship a Zod adapter still lack a Valibot one or add it late.
- OpenAPI export is thinner than Zod's — more hand-writing required.
5. ArkType — TypeScript syntax, executable at runtime
ArkType drew attention with its v2 GA in 2024. Different premise:
"Take a TypeScript expression as a string and turn it into a runtime schema."
Same User schema:
import { type } from 'arktype'
const User = type({
email: 'string.email',
age: 'number.integer >= 0',
name: 'string > 0',
})
// type inference: TS understands the expression precisely
type User = typeof User.infer
const out = User({ email: 'foo@example.com', age: 30, name: 'Foo' })
if (out instanceof type.errors) {
console.error(out.summary)
} else {
// out is the validated value
}
The trick: an expression like 'number.integer >= 0' is parsed by TS template literal types into a precise type. ArkType defines a mini DSL, parses it at the type level, and produces a schema whose inferred type matches a hand-written TS type 1:1.
What ArkType does differently
- TS expressions directly —
'string | number','(string | number)[]>=2'feel native. - Type-first —
typeof User.inferis instant; no extra.infercall cost (it's a type-level operation). - Built-in JSON Schema export —
User.toJsonSchema()is one line. - Pre-compiled validators — internally builds a validation function at schema definition time.
Object form
You can also pass a TS object directly:
import { type } from 'arktype'
const Post = type({
id: 'number.integer',
title: 'string > 0',
tags: 'string[]',
author: {
id: 'number',
name: 'string',
},
publishedAt: 'Date',
})
ArkType strengths
- Best-in-class TS affinity — near-zero learning cost if you know TS well.
- Built-in JSON Schema — no external package.
- Fast runtime — pre-compiled validators.
- Calling the schema is the validator —
User(value), intuitive.
ArkType weaknesses
- Ecosystem still smaller — tRPC and React Hook Form adapters exist but aren't as thick as Zod's.
- Less material for error-message localization and customization.
- Async validation is partial — sync-focused design.
- The DSL strings take getting used to —
'number.integer >= 0'feels like magic at first.
6. TypeBox — What you write is JSON Schema
TypeBox emerged in 2021, was loved by the Fastify camp, and got a second wind in the AI tool-calling era.
"My schema is literally JSON Schema. Compile with AJV and you've got the fastest runtime."
Same User schema:
import { Type, Static } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
const User = Type.Object({
email: Type.String({ format: 'email' }),
age: Type.Integer({ minimum: 0, maximum: 150 }),
name: Type.String({ minLength: 1 }),
})
type User = Static<typeof User>
const ok = Value.Check(User, unknown)
const errors = [...Value.Errors(User, unknown)]
What Type.Object({ ... }) returns IS a JSON Schema document. JSON.stringify(User) gives a standard JSON Schema.
Pairing with AJV — fastest runtime
import Ajv from 'ajv'
import addFormats from 'ajv-formats'
const ajv = addFormats(new Ajv())
const check = ajv.compile(User)
const ok = check(value) // compiled function — very fast
AJV compiles JSON Schema into V8-friendly code. The result is generally the fastest runtime validator in this category.
TypeBox + Fastify
import Fastify from 'fastify'
const app = Fastify().withTypeProvider<TypeBoxTypeProvider>()
app.post('/users', {
schema: { body: User, response: { 200: User } },
}, async (req) => {
// req.body is typed as User automatically
return req.body
})
The schema doubles as OpenAPI response spec, runtime validator, and response serializer. One definition, four jobs.
TypeBox for AI tool calling
OpenAI, Anthropic, and Gemini function/tool definitions are JSON Schema.
const GetWeather = Type.Object({
city: Type.String(),
units: Type.Optional(Type.Union([Type.Literal('c'), Type.Literal('f')])),
})
const tool = {
type: 'function',
function: {
name: 'get_weather',
description: 'Current weather for a given city',
parameters: GetWeather, // already JSON Schema
},
}
No conversion via zod-to-json-schema. Nothing lost in translation (format, description, etc.).
TypeBox strengths
- JSON Schema as-is — direct line to OpenAPI, AI tool calling, AJV.
- Fastest runtime with AJV — optimal for hot-path validation.
- Plenty of middleware for Fastify, Hono, Express.
- Very small bundle —
Type.*itself is light.
TypeBox weaknesses
- Slightly verbose API —
Type.Object({ x: Type.String() })feels long. - No chaining flavor — you'll miss
z.string().email().min(5). - Ecosystem leans toward Fastify — fewer React form adapters than Zod/Valibot.
- Async is weak — JSON Schema is sync-centric.
7. Effect Schema — The Effect ecosystem's validator
Effect is a Scala/Haskell-influenced full-stack TS functional framework. Effect Schema is its data validation/transform module.
"Validation is bidirectional transformation. And every error belongs in the type."
Same User schema:
import { Schema } from 'effect'
const User = Schema.Struct({
email: Schema.String.pipe(Schema.email()),
age: Schema.Number.pipe(Schema.int(), Schema.between(0, 150)),
name: Schema.NonEmptyString,
})
type User = Schema.Schema.Type<typeof User>
const decode = Schema.decodeUnknownSync(User)
const out = decode({ email: 'foo@example.com', age: 30, name: 'Foo' })
decodeUnknownSync is the sync entry. decodeUnknown/decode returns an Effect value — composable inside the Effect stack.
What Effect Schema does differently
- Bidirectional
decode↔encode— JSON to domain, domain to JSON. Date, BigDecimal, Brand types are natural. - Typed errors —
ParseErrorlives in the type, so missing error handling is a compile error. - Composition —
Schema.compose,Schema.transform,Schema.filter. - Built-in JSON Schema —
JSONSchema.make(User)in one line.
Bidirectional transform example
import { Schema } from 'effect'
// Convert ISO string -> domain Date both ways
const DateFromString = Schema.transform(Schema.String, Schema.Date, {
decode: (s) => new Date(s),
encode: (d) => d.toISOString(),
})
const Event = Schema.Struct({
name: Schema.NonEmptyString,
at: DateFromString,
})
const decoded = Schema.decodeUnknownSync(Event)({ name: 'Launch', at: '2026-05-14T00:00:00Z' })
// ^ decoded.at is a real Date
Effect Schema strengths
- Elegant bidirectional validation/transform — what other libraries make you hand-write naturally falls out.
- Typed errors block missing error handling.
- Composable with Effect's full stack — HTTP, DB, cache, retry as one model.
- Built-in JSON Schema.
Effect Schema weaknesses
- Heavy learning and bundle cost if you don't use Effect — Effect core comes along.
- Reads longer than chaining —
.pipe(Schema.email())feels awkward until it clicks. - Ecosystem — form and tRPC adapters are partial.
- Learning curve — you must learn Effect. Category-theory and functional fluency helps.
8. Same schema, five libraries — User(email + age + name)
Five side by side. Same semantics.
8.1 Zod
import { z } from 'zod'
const User = z.object({
email: z.string().email(),
age: z.number().int().min(0).max(150),
name: z.string().min(1),
})
type User = z.infer<typeof User>
8.2 Valibot
import * as v from 'valibot'
const User = v.object({
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
name: v.pipe(v.string(), v.minLength(1)),
})
type User = v.InferOutput<typeof User>
8.3 ArkType
import { type } from 'arktype'
const User = type({
email: 'string.email',
age: '0 <= number.integer <= 150',
name: 'string > 0',
})
type User = typeof User.infer
8.4 TypeBox
import { Type, Static } from '@sinclair/typebox'
const User = Type.Object({
email: Type.String({ format: 'email' }),
age: Type.Integer({ minimum: 0, maximum: 150 }),
name: Type.String({ minLength: 1 }),
})
type User = Static<typeof User>
8.5 Effect Schema
import { Schema } from 'effect'
const User = Schema.Struct({
email: Schema.String.pipe(Schema.email()),
age: Schema.Number.pipe(Schema.int(), Schema.between(0, 150)),
name: Schema.NonEmptyString,
})
type User = Schema.Schema.Type<typeof User>
Comparison — same schema, different philosophies
| Library | Lines | Style | Inference call |
|---|---|---|---|
| Zod | Short | Chaining | z.infer |
| Valibot | Medium | Function composition (pipe) | v.InferOutput |
| ArkType | Shortest | TS DSL string | typeof X.infer |
| TypeBox | Medium | Type builder | Static |
| Effect Schema | Medium | .pipe chaining | Schema.Schema.Type |
Zod and ArkType win readability. Valibot and TypeBox win bundle and tree-shaking. Effect Schema wins when you need transforms and typed errors.
9. The AI tool-calling era — JSON Schema is mandatory
The biggest trend in 2024–2025: every major LLM accepts tool definitions as JSON Schema.
9.1 OpenAI function calling
const tools = [{
type: 'function',
function: {
name: 'get_weather',
description: 'Get the current weather in a given city',
parameters: {
type: 'object',
properties: {
city: { type: 'string' },
units: { type: 'string', enum: ['c', 'f'] },
},
required: ['city'],
},
},
}]
parameters is standard JSON Schema. Either write it raw or convert from a TS schema library.
9.2 Anthropic tool use
const tools = [{
name: 'get_weather',
description: 'Get the current weather in a given city',
input_schema: {
type: 'object',
properties: {
city: { type: 'string' },
units: { type: 'string', enum: ['c', 'f'] },
},
required: ['city'],
},
}]
Only the key name differs (input_schema). Shape is identical.
9.3 JSON Schema conversion per library
- Zod —
@asteasolutions/zod-to-openapi,zod-to-json-schema, and the OpenAI SDK accepts Zod directly viazodFunction. Anthropic SDK is adding direct Zod support too. - Valibot —
@valibot/to-json-schema(official). - ArkType —
User.toJsonSchema()built-in. - TypeBox — no conversion needed. The definition is already JSON Schema.
- Effect Schema —
JSONSchema.make(User)built-in.
9.4 What gets lost in conversion
JSON Schema is narrower than TS expressions. Conversion frequently drops:
- Bidirectional transforms like
z.transform/v.transform— JSON Schema only describes input validation. - Brand types from
z.brand— flatten to a plainstringetc. - Custom
refinefunctions — anything not expressible in JSON Schema disappears. - Recursive schemas —
$refsupport is uneven across libraries and LLMs. Flatten or cap recursion depth for safety.
9.5 OpenAI structured outputs and strict mode
The structured outputs feature OpenAI launched in late 2024 enforces a JSON Schema subset (e.g., additionalProperties: false, required fields, no $ref, etc.). If your TS library's emitted JSON Schema breaks those rules, OpenAI rejects it.
Practical tips:
- Keep tool schemas as simple as possible (flat,
additionalProperties: false). - Use
descriptionaggressively — the best hint for the LLM. - Avoid recursion and
$ref; flatten LLM-friendly. - Sanity-check the converted output — print the resulting JSON Schema and verify it matches intent.
10. Seven-axis deep comparison
10.1 Bundle size
Roughly, with a small schema usage profile.
| Library | Small usage | Large usage | Notes |
|---|---|---|---|
| Valibot | Smallest (hundreds of B) | Small | Pinnacle of tree-shaking |
| TypeBox | Very small | Small | Type.* is itself light |
| ArkType | Small | Medium | Parser ships with it |
| Zod 4 | Medium (big drop) | Medium | v4-mini even smaller |
| Zod 3 | Large | Large | Weak tree-shaking |
| Effect Schema | Large | Large | Includes Effect core |
| Yup | Medium | Medium | |
| Joi | Large | Large | Low browser affinity |
10.2 TS inference quality
| Library | Inference | Notes |
|---|---|---|
| ArkType | Best | TS expressions as-is |
| Zod 4 | Very good | z.infer everywhere |
| Valibot | Very good | InferOutput/InferInput |
| Effect Schema | Very good | Includes bidirectional types |
| TypeBox | Good | Static |
| Yup | Fair | Inference is weak |
| Joi | Weak | Hand-write types |
10.3 JSON Schema export
| Library | Support | How |
|---|---|---|
| TypeBox | Native | Definition itself |
| ArkType | Built-in | .toJsonSchema() |
| Effect Schema | Built-in | JSONSchema.make() |
| Valibot | Official external | @valibot/to-json-schema |
| Zod | External | zod-to-json-schema, @asteasolutions/zod-to-openapi |
| Yup | Thin | Unofficial converters |
| Joi | None | Write by hand |
10.4 Async validation
| Library | Support | Pattern |
|---|---|---|
| Zod | Strong | z.string().refine(async ...) + parseAsync |
| Valibot | Strong | parseAsync/safeParseAsync |
| Effect Schema | Strong | Effect is async by nature |
| ArkType | Partial | Sync-focused |
| TypeBox | Weak | Sync-only design |
10.5 OpenAPI export
| Library | Support | Tooling |
|---|---|---|
| TypeBox | Very good | Fastify + Type Provider |
| ArkType | Good | .toJsonSchema() direct |
| Zod | Good | @asteasolutions/zod-to-openapi, @hono/zod-openapi |
| Effect Schema | Good | Effect HTTP + OpenAPI adapters |
| Valibot | Good | @valibot/to-json-schema |
10.6 Runtime cost (rough, hot-path)
| Library | Validation speed | Notes |
|---|---|---|
| TypeBox + AJV (compile) | Fastest | V8-friendly compiled code |
| Valibot | Very fast | Simple function composition |
| ArkType | Fast | Pre-compiled validators |
| Zod 4 | Fast | Big improvement |
| Effect Schema | Medium–fast | Effect overhead |
| Zod 3 | Medium | Weak on deep objects |
| Joi | Slow | Heavy older API |
(Numbers depend on the case. Use each library's official benchmarks and measure on your own workload.)
10.7 Ecosystem (adapters and integrations)
| Library | tRPC | RHF | OpenAI/Anthropic SDK | Fastify | Hono | Next.js |
|---|---|---|---|---|---|---|
| Zod | First-class | First-class | First-class | OK | OK | First-class |
| Valibot | Supported | First-class (@hookform/resolvers/valibot) | External convert | OK | OK | OK |
| ArkType | Supported | Supported | External convert | OK | OK | OK |
| TypeBox | Partial | Partial | Direct | First-class | First-class (@hono/typebox-validator) | OK |
| Effect Schema | Partial | Partial | External convert | OK | OK | OK |
11. Yup, Joi, Superstruct — Alive but…
11.1 Yup
Standard from the Formik era. Still common in form validation but new adoption is shrinking. Weaker TS inference and heavier bundle than Zod/Valibot.
import * as yup from 'yup'
const User = yup.object({
email: yup.string().email().required(),
age: yup.number().integer().min(0).max(150).required(),
name: yup.string().min(1).required(),
})
11.2 Joi
The old Node backend standard. Low browser affinity (large bundle, deep deps). Still very present in legacy server codebases.
const Joi = require('joi')
const User = Joi.object({
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(150).required(),
name: Joi.string().min(1).required(),
})
11.3 Superstruct
Small, lightweight validation. Friendlier than io-ts but a smaller ecosystem than Zod/Valibot. A solid pick for small projects and internal library validation.
import { object, string, number, refine } from 'superstruct'
const Email = refine(string(), 'email', (v) => /.+@.+/.test(v))
const User = object({
email: Email,
age: number(),
name: string(),
})
11.4 io-ts
The traditional functional camp. Effect Schema is rapidly taking that seat. Few new projects start with io-ts voluntarily.
12. Standard Schema — Cross-library compatibility
Around late 2024 to 2025 a Standard Schema spec emerged — the minimum interface validation libraries agreed on. The point: let tools like tRPC, Hono, and React Hook Form accept Zod/Valibot/ArkType through a single adapter.
Core interface (simplified):
interface StandardSchemaV1<Input = unknown, Output = Input> {
'~standard': {
version: 1
vendor: string
validate: (value: unknown) => StandardSchemaV1.Result<Output> | Promise<StandardSchemaV1.Result<Output>>
types?: { input: Input; output: Output }
}
}
Zod, Valibot, ArkType, and Effect Schema all implement this. The upshot — library-switching cost dropped. Coexisting libraries in one codebase, or migrating between them, is cheaper than before.
That said, Standard Schema is the minimum common denominator. Library-specific features (transform, refine, brand…) still need explicit adapter work.
13. What to pick when — An honest verdict
The honest answer is, as always, "it depends." But patterns exist.
Pick Zod 4 when
- You need the largest ecosystem. tRPC, Next.js, React Hook Form, OpenAI/Anthropic SDKs all first-class.
- The thickest pile of tutorials, docs, and Stack Overflow answers matters.
- KB-level bundle differences don't bite (backend or full-stack).
- The team already has Zod experience.
Pick Valibot when
- Frontend or edge is the priority. Bundle is the cost.
- You prefer functional composition.
- React Hook Form, React Router, Next.js client-side form validation.
- "Same job as Zod, smaller" is a clear motivation.
Pick ArkType when
- You need best-in-class TS inference.
- The "TS expression as schema" worldview clicks.
- Built-in JSON Schema export (avoid external deps) is a must.
- The team can adapt to a mini DSL.
Pick TypeBox when
- Building API backends — Fastify, Hono, Express + OpenAPI.
- AI tool calling — feed JSON Schema directly to LLMs.
- Hot-path validation — AJV-compiled is the fastest you'll get.
- Schema = OpenAPI = validator = serializer. One def, four jobs.
Pick Effect Schema when
- The team is going all-in on Effect.
- Bidirectional encoding and transforms are core (Date, Brand, BigDecimal).
- You want the compiler to enforce error handling via typed errors.
- A functional / category-theory friendly team.
Pick Yup, Joi, Superstruct when
- Maintaining legacy.
- Yup — Formik-based code.
- Joi — Hapi.js backends.
- Superstruct — dependency-minimal small libraries.
Anti-checklist (patterns to avoid)
- Three validation libraries in one codebase — even with Standard Schema, learning and debug cost piles up.
- "Valibot is smaller, so we switched from Zod" decided without measurement — large schemas narrow the gap.
- TypeBox for all form validation — form adapter ecosystem is thinner. Zod/Valibot fit naturally.
- Sending Zod straight to AI tool calls — convert and inspect the resulting JSON Schema.
- Stuffing
z.refine/v.checkwith logic that won't convert cleanly — that validation disappears after conversion. Keep core checks convertible.
Epilogue — Schemas are the truth at the API boundary
Picking a validation library is a tradeoff between how small, how familiar, how far you can take it. No single winner. But the May 2026 landscape is clear.
- JSON Schema interop is now a Tier-1 requirement — AI tool calling forced it. Zod caught up with external packages; TypeBox/ArkType/Effect Schema are native.
- The bundle race isn't over — Valibot set the bar; Zod 4 closed a lot of ground. The gap narrows, but it's still meaningful.
- TS inference is table stakes — weak inference kills a library's chance of new adoption.
- Standard Schema lowered switching cost — "pick once for life" is over.
- One schema for the whole stack is increasingly the norm — the same schema validates server, hydrates client forms, generates docs, and defines AI tool calls.
Decision checklist
- API boundary the top priority? — TypeBox, or Zod +
zod-to-openapi. - Forms / bundle / edge the top priority? — Valibot or Zod 4-mini.
- Need best TS inference? — ArkType.
- Centered on AI tool-calling JSON Schema? — TypeBox or ArkType.
- All-in on Effect? — Effect Schema.
- Care about ecosystem depth and learning resources? — Zod.
- Async validation an everyday thing? — Zod, Valibot, Effect Schema.
- Need bidirectional transforms or brand types? — Effect Schema or Zod transforms.
Anti-patterns
- Switching libraries without measuring.
- Sending the conversion result to AI tools without inspecting it — missing
additionalProperties/requiredhappens often. - Putting core validation in
refinethat JSON Schema can't represent — disappears in conversion. - TypeBox for forms — RHF adapter is weak. Zod/Valibot are natural.
- Repeating
z.parseon the same large object per request — for hot paths consider compiled validators (TypeBox + AJV). - Two sources of truth for one schema — DB shape and validation schema defined in different places and drifting.
- Skipping error-message customization — library default messages leak to users.
Coming up next
Candidates: Debugging AI tool-calling JSON Schema — common OpenAI structured outputs and Anthropic tool use mistakes, One month migrating a 50k-line codebase to Zod 4, Valibot deep dive — what the tree-shaker actually drops.
"Validation is the boundary of code. Code with clean boundaries travels far."
— TypeScript Data Validation 2026, end.
References
- Zod official
- Zod GitHub — colinhacks/zod
- Zod v4 release notes
- Valibot official
- Valibot GitHub — fabian-hiller/valibot
@valibot/to-json-schema- ArkType official
- ArkType GitHub — arktypeio/arktype
- TypeBox README
- TypeBox + Fastify Type Provider
- Effect official
- Effect Schema docs
- Standard Schema official
- Standard Schema GitHub
- AJV — JSON Schema validator
zod-to-json-schema@asteasolutions/zod-to-openapi@hono/zod-openapi@hookform/resolvers- OpenAI function calling docs
- OpenAI structured outputs announcement
- Anthropic tool use docs
- Yup official
- Joi official
- Superstruct official
- io-ts official
- tRPC official
- React Hook Form