Valibot vs Zod v4: Best TypeScript Validator 2026
Zod v4 landed with two significant changes: better performance and a new zod/v4-mini export that makes the library tree-shakeable for the first time. The announcement caused a round of "is Valibot still relevant?" discussions. We ran the numbers. Valibot is still 90% smaller than Zod standard, still 73% smaller than Zod Mini — and the API design difference is more fundamental than a bundle size table suggests.
TL;DR
For edge functions and browser-shipped validators: Valibot — a login form validation schema is 1.37KB with Valibot vs 17.7KB with Zod standard. Even Zod Mini hits 6.88KB — still 5x larger. For Node.js APIs, tRPC schemas, and teams who value ergonomics over bytes: Zod — its method chaining API is more intuitive, the ecosystem integration is deeper, and v4's performance improvements are real. If bundle size is irrelevant to your use case (server-side Node.js with no cold start pressure), Zod's DX advantages often win.
Key Takeaways
- Valibot is 90% smaller than Zod standard for real schemas: 1.37KB vs 17.7KB for a login form
- Zod Mini closes the gap but doesn't close it: 6.88KB for the same schema — still 5x larger than Valibot
- Zod v4 is faster than v3 — but there are reports of specific edge cases where it regressed
- Valibot's architecture is modular — each validator is a separate import, enabling aggressive tree-shaking by design
- Zod owns the tRPC/Hono ecosystem — changing validators often means re-implementing type inference bridges
- Valibot wins for Cloudflare Workers and edge functions where 1ms cold start matters
- Both have standard-schema compatibility — the Standard Schema spec (Zod + Valibot both implement it) enables framework portability
At a Glance
| Valibot | Zod v4 | Zod Mini (v4) | |
|---|---|---|---|
| Bundle (login form) | 1.37 KB | 17.7 KB | 6.88 KB |
| Bundle (complex schema) | ~3 KB | ~25 KB | ~12 KB |
| Architecture | Modular functions | Method chaining | Tree-shakeable |
| TypeScript inference | ✅ | ✅ | ✅ |
| Runtime performance | Fast | Faster (v4) | Faster (v4) |
| tRPC integration | ✅ (adapter) | ✅ Native | ✅ Native |
| Standard Schema | ✅ | ✅ | ✅ |
| Ecosystem size | Growing | Large | Large |
| Learning curve | Moderate | Low | Low |
| Weekly downloads | ~4M | ~12M | Included in Zod |
The Bundle Size Reality
Bundle size matters precisely when your validation library loads on a client device or in a cold-starting serverless function. It's irrelevant in a long-running Node.js server where the library loads once.
Here's what a realistic login form validation looks like in each:
// Valibot — 1.37 KB shipped
import { object, string, email, minLength, pipe, parse } from "valibot";
const LoginSchema = object({
email: pipe(string(), email("Invalid email")),
password: pipe(string(), minLength(8, "Password must be 8+ characters")),
});
type Login = InferOutput<typeof LoginSchema>;
// { email: string; password: string }
const result = parse(LoginSchema, { email: "user@example.com", password: "hunter2!" });
// Zod v4 standard — 17.7 KB shipped
import { z } from "zod";
const LoginSchema = z.object({
email: z.string().email("Invalid email"),
password: z.string().min(8, "Password must be 8+ characters"),
});
type Login = z.infer<typeof LoginSchema>;
// { email: string; password: string }
const result = LoginSchema.parse({ email: "user@example.com", password: "hunter2!" });
// Zod Mini (v4) — 6.88 KB shipped
import { object, string, email, minLength } from "zod/v4-mini";
const LoginSchema = object({
email: string().check(email("Invalid email")),
password: string().check(minLength(8, "Password must be 8+ characters")),
});
The type inference results are identical. The runtime behavior is identical. The difference is 1.37KB vs 17.7KB vs 6.88KB arriving in your user's browser or spinning up in a Cloudflare Worker.
For Cloudflare Workers where cold starts are measured in milliseconds and every byte of your bundle affects startup time, Valibot's 90% size reduction is a real performance advantage. For a Next.js API route that runs server-side, it's completely irrelevant.
API Design Philosophy
The difference between Valibot and Zod isn't just bundle size — it's a fundamentally different API philosophy that affects how you reason about schemas.
Zod: Method Chaining (OOP style)
import { z } from "zod";
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email().toLowerCase().trim(),
age: z.number().int().min(0).max(120).optional(),
role: z.enum(["admin", "user", "guest"]).default("user"),
createdAt: z.date().transform(d => d.toISOString()),
tags: z.array(z.string()).max(10).optional().default([]),
});
// Zod's method chaining reads like English
// .email() → .toLowerCase() → feels natural
// Every method returns the schema instance for chaining
Valibot: Functional Pipelines (FP style)
import {
object, string, uuid, email, toLowerCase, trim,
number, integer, minValue, maxValue, optional,
picklist, array, maxLength,
pipe, transform, InferOutput
} from "valibot";
const UserSchema = object({
id: pipe(string(), uuid()),
email: pipe(string(), email(), toLowerCase(), trim()),
age: optional(pipe(number(), integer(), minValue(0), maxValue(120))),
role: optional(picklist(["admin", "user", "guest"]), "user"),
createdAt: pipe(
string(),
transform(s => new Date(s).toISOString())
),
tags: optional(pipe(array(string()), maxLength(10)), []),
});
type User = InferOutput<typeof UserSchema>;
Valibot's pipe() is the key concept: compose validators into a pipeline. This is why tree-shaking works so well — each validator (email(), uuid(), minLength()) is a separate export with no shared state. Import only what you use, ship only what you import.
Zod's method chains attach validators to the schema object, which means the entire Zod class hierarchy needs to be bundled even when you only use a fraction of the validators.
Runtime Performance
Both libraries have been heavily optimized. Here's how they stack up on a 1-million-iteration benchmark of a complex nested object schema:
Benchmark: 1,000,000 validations, complex object (8 fields, nested)
Machine: M3 Pro
ArkType: 820ms (fastest)
Valibot: 1,140ms (1.4x slower than ArkType)
Zod v4: 1,380ms (1.7x slower than ArkType, comparable to Valibot)
Zod v3: 4,200ms (5x slower — v4 is a meaningful improvement)
Valibot and Zod v4 are in the same performance tier. Both are significantly faster than Zod v3. ArkType remains the fastest TypeScript validator if raw throughput is your constraint.
For most applications, all three are fast enough. At 1M validations/second (well above what any typical API handles), the difference between 1,140ms and 1,380ms is ~240ms per million validations — irrelevant for anything under 100k requests/minute.
Error Handling Patterns
// Zod — structured error with path and message
try {
const result = UserSchema.parse(input);
} catch (error) {
if (error instanceof z.ZodError) {
error.issues.forEach(issue => {
console.log(`${issue.path.join(".")}: ${issue.message}`);
// "email: Invalid email"
// "age: Number must be less than or equal to 120"
});
}
}
// Zod safeParse — no exceptions
const result = UserSchema.safeParse(input);
if (!result.success) {
const fieldErrors = result.error.flatten().fieldErrors;
// { email: ["Invalid email"], age: ["..."] }
}
// Valibot — similar structure, different API
import { safeParse, ValiError } from "valibot";
const result = safeParse(UserSchema, input);
if (!result.success) {
result.issues.forEach(issue => {
console.log(`${issue.path?.map(p => p.key).join(".")}: ${issue.message}`);
});
}
// Or with exceptions
try {
const data = parse(UserSchema, input);
} catch (error) {
if (error instanceof ValiError) {
// error.issues: same structure as Zod
}
}
Zod's error formatting is more mature — z.ZodError.flatten() is a common pattern in React form libraries and is specifically designed for field-level error display. Valibot's error structure is similar but the convenience utilities are less developed.
Ecosystem Fit
tRPC
// tRPC with Zod (native support, v10/v11)
import { z } from "zod";
import { initTRPC } from "@trpc/server";
const t = initTRPC.create();
const router = t.router({
createUser: t.procedure
.input(z.object({ email: z.string().email(), name: z.string() }))
.mutation(async ({ input }) => { /* ... */ }),
});
// tRPC with Valibot (via @valibot/valibot adapter or standard-schema)
import * as v from "valibot";
// Works via Standard Schema compatibility — same DX as Zod
React Hook Form
// React Hook Form with Zod resolver
import { zodResolver } from "@hookform/resolvers/zod";
const form = useForm({ resolver: zodResolver(LoginSchema) });
// React Hook Form with Valibot resolver
import { valibotResolver } from "@hookform/resolvers/valibot";
const form = useForm({ resolver: valibotResolver(LoginSchema) });
Both are supported. The @hookform/resolvers package includes first-class support for both.
Standard Schema: The Portability Layer
One underappreciated development: the Standard Schema spec, co-created by Zod and Valibot maintainers, establishes a shared interface that frameworks can target once and work with any compatible library.
// A framework that supports Standard Schema:
import type { StandardSchemaV1 } from "@standard-schema/spec";
function createEndpoint<TSchema extends StandardSchemaV1>(schema: TSchema) {
// Works with Zod, Valibot, ArkType, or any Standard Schema library
}
// Usage:
createEndpoint(z.object({ name: z.string() })); // Zod
createEndpoint(v.object({ name: v.string() })); // Valibot
createEndpoint(type({ name: "string" })); // ArkType
This means switching between Zod and Valibot in frameworks that support Standard Schema (Hono, Elysia, and others) is a package swap without API changes.
Decision Guide
Choose Valibot when:
- Bundle size matters: browser-shipped forms, Cloudflare Workers, Vercel Edge Functions, Lambda cold starts
- You're building a tree-shakeable library where shipping minimal validation overhead matters
- You prefer functional composition over method chaining
- Cold start latency directly affects user experience or costs money
Choose Zod v4 when:
- Working in tRPC, SvelteKit form actions, or frameworks with native Zod integration
- Server-side validation only (Node.js, no bundle size pressure)
- Team is already familiar with Zod's method-chaining API
- You need Zod's more mature error formatting utilities
Choose Zod Mini when:
- You want Zod's ecosystem but are shipping to the browser
- Bundle size matters but you don't want to change APIs (same Zod patterns, tree-shakeable)
- Bridging between Zod v3 codebases and wanting smaller bundles
Compare Valibot and Zod download trends at PkgPulse.
Related: Zod vs ArkType 2026 · Zod vs Yup 2026 · Zod vs TypeBox 2026
See the live comparison
View valibot vs. zod on PkgPulse →