· zod / valibot / schema-validation
Zod vs Valibot: Schema-Validation Tradeoffs in 2026
Valibot shrinks your form validator 12× over Zod — but Zod wins on ecosystem, i18n, and Astro Actions. Real bundle numbers, real code, one recommendation.
By Ethan
1,883 words · 10 min read
Pick Zod if you’re on a Node.js server, already have Zod in the stack, or use Astro Actions. Pick Valibot if you deploy to the browser or edge and have 15 KB to account for — Valibot’s tree-shaken bundle for a typical login form is 1.37 kB against Zod’s 17.7 kB, a 12× gap that shows up in cold-start metrics. Both validate TypeScript schemas correctly in 2026. The decision is about bundle bytes, ecosystem fit, and team inertia — not correctness.
Who this is for
TypeScript developers choosing a schema validator for a new project, or evaluating a move from one to the other. If you’re already on Zod and satisfied, the ecosystem and verdict sections will tell you the specific scenarios where the calculus changes.
Version pins
All numbers below are specific to these releases:
| Library | Version | Released |
|---|---|---|
| Zod | v4.4.3 | May 4, 2026 |
| Valibot | v1.4.0 | May 5, 2026 |
At a glance
| Metric | Zod v4.4.3 | Valibot v1.4.0 |
|---|---|---|
| Monthly downloads | ~595M | ~32.9M |
| GitHub stars | 42,732 | 8,690 |
| Full bundle (gzip) | 60.3 KB | 14.4 KB |
| Typical form bundle (esbuild) | ~17.7 kB | ~1.37 kB |
| API style | Chained OOP | Functional composition |
| Tree-shaking | Partial (excellent in Zod Mini) | Excellent by design |
| TypeScript requirement | v5.5+ strict | v5+ |
| Zero dependencies | Yes | Yes |
| License | MIT | MIT |
| Last release | May 2026 | May 2026 |
Both are actively maintained, zero-dependency, MIT-licensed. Zod has 17× the download volume. The gap on bundle size is the only axis where one decisively beats the other.
Bundle size
This is the only number that actually changes your deployment.
Bundlephobia (measured May 21, 2026) puts the full packages at:
| Zod 4.4.3 | Valibot 1.4.0 | |
|---|---|---|
| Minified | 274.7 KB | 83.4 KB |
| Gzip | 60.3 KB | 14.4 KB |
But importing an entire library is not how you use these tools. The Valibot comparison guide builds a realistic login-form validator with esbuild and measures what actually ships to the client:
| Library | esbuild output |
|---|---|
| Valibot v1 | 1.37 kB |
| Zod Mini | 6.88 kB |
| Zod (standard) | 17.7 kB |
Why the gap? Valibot’s functions are standalone. Importing v.string() and v.email() bundles those two functions and nothing else. Zod’s chained class API means importing the z namespace pulls in the full prototype chain.
Zod v4 introduced Zod Mini (import { z } from "zod/mini") — a functional API similar to Valibot’s style that cuts the bundle to ~3.94 kB with Rolldown. That narrows the gap, but Valibot still wins at 1.37 kB.
On a Node.js API server, a 16 kB difference is invisible — a cold-start rounding error. On a Cloudflare Worker with tight CPU-time limits, or in a Next.js client component where every kilobyte affects Time to Interactive, it matters. Know your deployment target before treating this number as decisive.
API walkthrough
Parse and safeParse
Both libraries give you a throwing parse and a safe variant that returns a discriminated union.
Zod v4:
import { z } from "zod"
const UserSchema = z.object({
name: z.string(),
age: z.number().int()
})
// Throws ZodError on failure
const user = UserSchema.parse({ name: "Alice", age: 30 })
// Never throws — success: true | success: false
const result = UserSchema.safeParse(input)
if (result.success) {
console.log(result.data)
} else {
console.error(result.error.issues)
}
// Async variant for async refinements
const user2 = await UserSchema.parseAsync(input)
Valibot v1:
import * as v from "valibot"
const UserSchema = v.object({
name: v.string(),
age: v.pipe(v.number(), v.integer())
})
// Throws ValiError on failure
const user = v.parse(UserSchema, { name: "Alice", age: 30 })
// Never throws
const result = v.safeParse(UserSchema, input)
if (result.success) {
console.log(result.output) // .output, not .data
} else {
console.error(result.issues)
}
// Async variant
const user2 = await v.parseAsync(UserSchema, input)
The shape is nearly identical. Key surface differences: Valibot uses standalone functions (v.parse(schema, input)) where Zod uses methods on the schema object (schema.parse(input)). Valibot’s success result uses .output; Zod uses .data.
Refine, transform, coerce
Zod:
// Custom validation
z.string().refine(s => s.startsWith("https"), "Must be HTTPS URL")
// Transform — output type changes from input type
z.string().transform(s => s.split(",")) // z.infer<> = string[]
// Coerce — wraps primitive constructors
z.coerce.number() // Number(input)
Valibot:
// check() is the equivalent of refine()
v.pipe(v.string(), v.check(s => s.startsWith("https"), "Must be HTTPS URL"))
// transform inside pipe
v.pipe(v.string(), v.transform(s => s.split(","))) // InferOutput<> = string[]
// No coerce namespace — explicit transform action
v.pipe(v.unknown(), v.transform(v => Number(v)))
Valibot’s v.pipe() makes each validation step an explicit argument. Zod’s method chaining reads more fluidly for simple schemas; Valibot’s pipe gets more readable once schemas involve multiple validation and transformation stages, because each step is visually separated.
Brand types
Both handle nominal typing at compile time with zero runtime cost.
Zod:
const UserId = z.string().brand<"UserId">()
type UserId = z.infer<typeof UserId> // string & { _brand: "UserId" }
Valibot:
const UserId = v.pipe(v.string(), v.brand("UserId"))
type UserId = v.InferOutput<typeof UserId> // string & Brand<"UserId">
No practical difference. Both produce the same TypeScript-only guard that disappears at runtime.
Runtime performance
Zod v4 was a ground-up rewrite. Official benchmarks show:
| Schema type | v4 speedup vs v3 |
|---|---|
| String parsing | 14.71× |
| Array parsing | 7.43× |
| Object parsing | 6.5× |
Valibot v1 has similar runtime throughput to Zod v4. Valibot was roughly 2× faster than Zod v3 — that advantage is gone. The Valibot comparison guide confirms both are now comparable.
For nearly every application, both parse thousands of objects per second without being the bottleneck. If raw throughput is your constraint and you need AOT-compiled speed, neither library is the answer — TypeBox or Typia are the tools for that. For everything else, runtime performance shouldn’t be your deciding factor.
Zod v4 also cut TypeScript compilation time significantly — a 100× reduction in type instantiation count vs v3. For large monorepos where tsc runs slowly over big schema files, that’s a real improvement.
Ecosystem support
Standard Schema changed the picture substantially. Any framework that adopted the spec supports Zod, Valibot, ArkType, TypeBox, and others interchangeably:
| Integration | Zod | Valibot | Notes |
|---|---|---|---|
| tRPC | ✅ | ✅ | Both via Standard Schema |
| React Hook Form | ✅ | ✅ | @hookform/resolvers ships both |
| Drizzle ORM | ✅ | ✅ | Both built into drizzle-orm core |
| TanStack Form | ✅ | ✅ | Both supported |
| Hono | ✅ | ✅ | Both listed |
| Vercel AI SDK | ✅ | ✅ | Both supported |
| SvelteKit Superforms | ✅ | ✅ | Both adapters available |
| Astro Actions | ✅ | ❌ | Astro bundles Zod; no official Valibot path |
Astro Actions is the one exception worth noting. Astro ships astro/zod as a bundled dependency — there’s no official Valibot adapter as of May 2026. If Astro Actions is in your stack, Zod is the path of least resistance.
For tRPC, React Hook Form, and Drizzle, the choice is neutral. Standard Schema means both work identically in these frameworks — no adapter penalty, no feature gap. If you’re still picking a TypeScript ORM, Drizzle vs Kysely covers the tradeoffs. If you’re weighing RPC approaches, tRPC vs GraphQL has the full comparison.
Error messages and i18n
Zod v4 ships three error-formatting utilities out of the box:
const result = z.object({ age: z.number() }).safeParse({ age: "old" })
z.flattenError(result.error)
// { formErrors: [], fieldErrors: { age: ["Expected number, received string"] } }
z.treeifyError(result.error) // nested structure matching schema shape
z.prettifyError(result.error) // human-readable string for logging
Built-in i18n via zod/locales covers 40+ languages:
import { de } from "zod/locales"
z.config(de()) // German error messages globally
Valibot v1 has a flatten(issues) utility for form-friendly output and the @valibot/i18n community package for translations — fewer locales than Zod’s built-in set, but the list is growing.
If you need i18n without adding a separate dependency, Zod’s 40 built-in locales are a genuine advantage. If your app is English-only, both are equivalent.
When to pick Zod vs Valibot
Pick Zod when:
- Server-side or Node.js — bundle size is irrelevant; stay with the dominant ecosystem choice
- Astro Actions — Zod is bundled, zero friction
- Already on Zod — migration costs real time; the bundle argument doesn’t apply to server code
- Built-in i18n — 40+ locales without a separate install
- Large TypeScript codebase — Zod v4’s reduced type instantiation count improves
tscspeed - Team knowledge and community — 595M monthly downloads means more tutorials, Stack Overflow answers, and starter templates default to Zod
Pick Valibot when:
- Edge functions or browser code — Cloudflare Workers, Vercel Edge, client-side bundles where 12–16 kB matters
- Greenfield in 2026 — Valibot v1 is stable and well-documented; the ecosystem gap is largely closed for Standard Schema consumers
- Functional composition preferred —
v.pipe()is more explicit than method chaining for complex multi-step schemas - tRPC / React Hook Form / Drizzle as your integrations — Standard Schema makes the choice neutral; pick based on bundle alone
Migration path
Valibot ships an official codemod and migration guide that handles most of the mechanical work:
- Automated codemod (CLI + online tool)
- Import swap (
zod→valibot) - Method chains →
v.pipe()restructuring - Name renames table (
and→intersect,catch→fallback,enum→picklist)
A medium-complexity schema layer with clear boundaries takes an afternoon, not a sprint.
Verdict
Server-side / full-stack Node.js: Zod. Ecosystem inertia, documentation depth, and 17× the adoption outweigh bundle savings you’ll never see in production metrics. Astro Actions locks you in anyway.
Edge, browser, or greenfield: Valibot. A 12× bundle reduction for a typical form validator is real money in cold-start environments. Valibot v1 is stable, Standard Schema means your framework integrations are identical, and the migration path back to Zod is automated if you change your mind.
If you’re already on Zod, stay on Zod — the argument for switching only holds for client-side or edge deployments, and migration costs real work. If you’re making a fresh choice today and deploying anywhere that counts bytes, Valibot is worth the unfamiliarity cost.
Validation failures in production surface as runtime errors. If you’re not already capturing them with full schema-path context, Sentry does this without extra instrumentation — you see which field failed, what the input was, and the full stack.
Want to go deeper on TypeScript type inference and how Zod and Valibot schemas compose with more advanced patterns? Frontend Masters has structured TypeScript courses that cover this territory.