Skip to content
Published on

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

Authors

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.

CategoryRepresentativeOne-line summary
Object builder (chaining)Zod, Yup, Joiz.string().email() style. Most familiar.
Modular functionsValibotstring([email()]). Tree-shake friendly.
TS syntax as schemaArkTypeUse TS type expressions at runtime.
JSON Schema nativeTypeBoxWhat you write is JSON Schema.
Functional + EffectEffect SchemaTyped errors, bidirectional encoding.
Lightweight assertionSuperstruct, io-tsSmall, 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.

AxisZod 4ValibotArkTypeTypeBoxEffect Schema
Bundle (core)MediumVery small (modular)SmallVery smallLarge (Effect core included)
TS inference qualityVery goodVery goodBest (TS expressions as-is)GoodVery good
JSON Schema exportExternal pkgExternal pkgBuilt-inBuilt-in (definition IS JSON Schema)Built-in
Async validationYesYesPartialNo (sync-focused)Yes
OpenAPI exportzod-to-openapi@valibot/to-json-schemaBuilt-inBuilt-in (Fastify/AJV)@effect/schema-openapi
Runtime costFast (large gain in v4)Very fastFastFastest (when AJV-compiled)Medium–fast
EcosystemLargestGrowing fastMediumAPI/Fastify campEffect 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.

  1. Chaining API familiarity. z.string().email().min(5) reads as intent. Near-zero learning cost for anyone coming from Joi or Yup.
  2. z.infer type inference. Derive TS types directly from the schema. Schema = single source of truth for the type.
  3. 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 zod and zod/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.discriminatedUnion carry a slight learning cost.
  • JSON Schema export needs an external package (@asteasolutions/zod-to-openapi etc.). 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.record
  • v.string, v.number, v.boolean, v.literal, v.date, v.bigint
  • v.pipe(schema, ...actions) — validator composition
  • v.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 InferOutput and InferInput are 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-firsttypeof User.infer is instant; no extra .infer call cost (it's a type-level operation).
  • Built-in JSON Schema exportUser.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 validatorUser(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 bundleType.* itself is light.

TypeBox weaknesses

  • Slightly verbose APIType.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 decodeencode — JSON to domain, domain to JSON. Date, BigDecimal, Brand types are natural.
  • Typed errorsParseError lives in the type, so missing error handling is a compile error.
  • CompositionSchema.compose, Schema.transform, Schema.filter.
  • Built-in JSON SchemaJSONSchema.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

LibraryLinesStyleInference call
ZodShortChainingz.infer
ValibotMediumFunction composition (pipe)v.InferOutput
ArkTypeShortestTS DSL stringtypeof X.infer
TypeBoxMediumType builderStatic
Effect SchemaMedium.pipe chainingSchema.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 via zodFunction. Anthropic SDK is adding direct Zod support too.
  • Valibot@valibot/to-json-schema (official).
  • ArkTypeUser.toJsonSchema() built-in.
  • TypeBox — no conversion needed. The definition is already JSON Schema.
  • Effect SchemaJSONSchema.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 plain string etc.
  • Custom refine functions — anything not expressible in JSON Schema disappears.
  • Recursive schemas$ref support 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 description aggressively — 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.

LibrarySmall usageLarge usageNotes
ValibotSmallest (hundreds of B)SmallPinnacle of tree-shaking
TypeBoxVery smallSmallType.* is itself light
ArkTypeSmallMediumParser ships with it
Zod 4Medium (big drop)Mediumv4-mini even smaller
Zod 3LargeLargeWeak tree-shaking
Effect SchemaLargeLargeIncludes Effect core
YupMediumMedium
JoiLargeLargeLow browser affinity

10.2 TS inference quality

LibraryInferenceNotes
ArkTypeBestTS expressions as-is
Zod 4Very goodz.infer everywhere
ValibotVery goodInferOutput/InferInput
Effect SchemaVery goodIncludes bidirectional types
TypeBoxGoodStatic
YupFairInference is weak
JoiWeakHand-write types

10.3 JSON Schema export

LibrarySupportHow
TypeBoxNativeDefinition itself
ArkTypeBuilt-in.toJsonSchema()
Effect SchemaBuilt-inJSONSchema.make()
ValibotOfficial external@valibot/to-json-schema
ZodExternalzod-to-json-schema, @asteasolutions/zod-to-openapi
YupThinUnofficial converters
JoiNoneWrite by hand

10.4 Async validation

LibrarySupportPattern
ZodStrongz.string().refine(async ...) + parseAsync
ValibotStrongparseAsync/safeParseAsync
Effect SchemaStrongEffect is async by nature
ArkTypePartialSync-focused
TypeBoxWeakSync-only design

10.5 OpenAPI export

LibrarySupportTooling
TypeBoxVery goodFastify + Type Provider
ArkTypeGood.toJsonSchema() direct
ZodGood@asteasolutions/zod-to-openapi, @hono/zod-openapi
Effect SchemaGoodEffect HTTP + OpenAPI adapters
ValibotGood@valibot/to-json-schema

10.6 Runtime cost (rough, hot-path)

LibraryValidation speedNotes
TypeBox + AJV (compile)FastestV8-friendly compiled code
ValibotVery fastSimple function composition
ArkTypeFastPre-compiled validators
Zod 4FastBig improvement
Effect SchemaMedium–fastEffect overhead
Zod 3MediumWeak on deep objects
JoiSlowHeavy 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)

LibrarytRPCRHFOpenAI/Anthropic SDKFastifyHonoNext.js
ZodFirst-classFirst-classFirst-classOKOKFirst-class
ValibotSupportedFirst-class (@hookform/resolvers/valibot)External convertOKOKOK
ArkTypeSupportedSupportedExternal convertOKOKOK
TypeBoxPartialPartialDirectFirst-classFirst-class (@hono/typebox-validator)OK
Effect SchemaPartialPartialExternal convertOKOKOK

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.check with 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

  1. Switching libraries without measuring.
  2. Sending the conversion result to AI tools without inspecting it — missing additionalProperties/required happens often.
  3. Putting core validation in refine that JSON Schema can't represent — disappears in conversion.
  4. TypeBox for forms — RHF adapter is weak. Zod/Valibot are natural.
  5. Repeating z.parse on the same large object per request — for hot paths consider compiled validators (TypeBox + AJV).
  6. Two sources of truth for one schema — DB shape and validation schema defined in different places and drifting.
  7. 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