Zod v4 vs Valibot 2026: TypeScript Validation
TypeScript validation libraries do one thing: take untyped input from the outside world and either produce a typed output or throw an error. But the differences in how Zod and Valibot approach that task — bundle size, API style, ecosystem integration, and async support — matter significantly depending on where you're running your validation code. In 2026, Zod v4 made the comparison much closer than it used to be.
TL;DR
Zod v4 closed the bundle-size gap that gave Valibot its initial edge. Zod v4 ships ~1.8KB (down from ~13KB in v3) and adds first-class async validation, pipe transforms, and improved error messages. Valibot still wins on tree-shaking granularity — you only bundle the exact validators you use. For most TypeScript projects: Zod v4 is the pragmatic default with the larger ecosystem. Valibot is the better choice when bundle size is critical (edge functions, CDN scripts) or when you need per-validator granularity.
Quick Comparison
| Zod v4 | Valibot | |
|---|---|---|
| Weekly Downloads | ~20M | ~500K |
| Bundle Size (min+gz) | ~1.8KB core / ~4KB full | ~0.3KB base + per-validator |
| API Style | Method chains | Functional/modular |
| TypeScript | ✅ Native | ✅ Native |
| Async Validation | ✅ Improved in v4 | ✅ First-class |
| tRPC Integration | ✅ First-class | ⚠️ Community |
| React Hook Form | ✅ Resolver | ✅ Resolver |
| License | MIT | MIT |
Key Takeaways
- Bundle size: Zod v4 ~1.8KB (down from ~13KB); Valibot ~0.3KB base + per-validator
- Ecosystem: Zod has 20M+ weekly downloads and integrates with every TypeScript library; Valibot ~500K
- API style: Zod is class-based chains; Valibot is functional/modular
- Error customization: both excellent, Valibot slightly more granular
- Async validation: Zod v4 improved significantly; Valibot had it from day one
The Bundle Size Story
Bundle size was the core reason Valibot was created. Fabian Hiller launched Valibot in 2023 specifically to solve the problem that Zod v3's ~13KB minimum bundle made it impractical for edge functions and browser-side validation in performance-sensitive contexts. The modular design meant each validator was a tree-shakeable function — import only what you use, pay only for what you ship.
Zod v4's 2025 release fundamentally changed this calculation. Adam Wathan and the Zod team rewrote the core for v4, reducing the minimum bundle from ~13KB to ~1.8KB. The full schema still runs ~4KB, but for most use cases, the size gap that justified Valibot's existence largely closed. The tree-shaking story is still better in Valibot — you truly pay per-validator — but Zod v4 is now viable in contexts where v3 was not.
Zod v3 (the old baseline):
Full bundle: ~13KB gzipped
Even if you use one validator: ~13KB
Tree-shaking: minimal (class-based, most code needed)
Valibot v0.x (the challenger):
Base: ~300 bytes
Each validator adds ~100-300 bytes
Use z.string().email(): ~600 bytes total
Use 10 validators: ~3KB total
Perfect tree-shaking by design
Zod v4 (2025):
Core: ~1.8KB gzipped
Full schema: ~4KB
Improved tree-shaking (not as granular as Valibot, but much better)
Real-world impact:
→ Edge function (Cloudflare Worker): Valibot still wins (0.5KB vs 2KB)
→ Next.js app (server only, no bundle concern): Zod v4 fine
→ Browser bundle with 5+ validators: comparable; Valibot marginally better
→ Library author (must minimize deps): Valibot or Zod v4 both viable
The practical implication: for most applications running server-side (Next.js App Router, Remix, Express), bundle size simply doesn't matter. Neither ~2KB nor ~4KB will affect your Lighthouse score. The bundle argument matters specifically for Cloudflare Workers (1MB limit), edge runtime code, browser-executed validation scripts, and npm packages that list Zod as a dependency.
API Comparison: Same Goal, Different Style
The most visible difference between Zod and Valibot is the API style. Zod uses method chains — you call .string() to get a string validator, then chain .min(1) for minimum length, .max(100) for maximum, .email() for email format validation. This is the fluent interface pattern: each method returns the validator with the additional constraint applied.
Valibot uses function composition via a pipe() function. Each validator is an independent function, and you compose them by passing them as arguments to pipe(). This is more aligned with functional programming patterns and enables better tree-shaking since each validator is a standalone module.
// ─── Defining a schema ───
// Zod v4 (method chains on class instances):
import { z } from 'zod';
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(['admin', 'user', 'viewer']),
createdAt: z.date(),
});
type User = z.infer<typeof UserSchema>;
// Valibot (functional, each validator is a separate function):
import * as v from 'valibot';
const UserSchema = v.object({
id: v.pipe(v.string(), v.uuid()),
name: v.pipe(v.string(), v.minLength(1), v.maxLength(100)),
email: v.pipe(v.string(), v.email()),
age: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150))),
role: v.picklist(['admin', 'user', 'viewer']),
createdAt: v.date(),
});
type User = v.InferOutput<typeof UserSchema>;
// Key style difference:
// Zod: z.string().min(1).max(100) — method chain
// Valibot: v.pipe(v.string(), v.minLength(1), v.maxLength(100)) — function composition
// Both are TypeScript-safe; it's a preference question
Neither style is objectively superior — they're expressions of different design philosophies. Zod's chains feel natural to developers familiar with fluent interfaces (jQuery, Eloquent, RxJS). Valibot's pipes feel natural to developers who prefer functional composition (Ramda, fp-ts). Both produce identical runtime behavior; the difference is purely in how you read and write the schema definitions.
Parsing and Error Handling
Both libraries support safe parsing (returns a result object instead of throwing) and throw-on-failure parsing. The error shapes differ, and the differences matter when you're integrating with form validation or building API response formatting.
Zod's .format() method on errors returns a nested object that mirrors your schema shape — ideal for mapping errors back to form fields. The .flatten() method gives you fieldErrors and formErrors arrays. Valibot returns an array of issues with path information.
// ─── Zod v4 parsing ───
const result = UserSchema.safeParse(rawInput);
if (!result.success) {
// result.error is a ZodError
console.log(result.error.format());
// {
// name: { _errors: ["String must contain at least 1 character(s)"] },
// email: { _errors: ["Invalid email"] },
// }
// Flat errors:
console.log(result.error.flatten());
// { fieldErrors: { name: [...], email: [...] }, formErrors: [] }
}
// Zod v4 improved: error maps are customizable globally or per-schema:
const UserSchema = z.object({
email: z.string().email({ message: "Please enter a valid email address" }),
age: z.number({ invalid_type_error: "Age must be a number" }).min(18, {
message: "You must be at least 18 years old",
}),
});
// ─── Valibot parsing ───
const result = v.safeParse(UserSchema, rawInput);
if (!result.success) {
// result.issues is an array of ValiError
for (const issue of result.issues) {
console.log(issue.path, issue.message);
// ['email'] "Invalid email"
}
}
// Valibot error customization (per-validator):
const UserSchema = v.object({
email: v.pipe(
v.string('Email is required'),
v.email('Please enter a valid email address')
),
age: v.pipe(
v.number('Age must be a number'),
v.minValue(18, 'You must be at least 18')
),
});
// Both approaches work well for form validation.
// Zod's .flatten() is particularly convenient for React Hook Form integration.
For React Hook Form users, both libraries provide resolvers via @hookform/resolvers. Zod's resolver has been around longer and has more community examples, but the Valibot resolver works the same way. The choice rarely comes down to form integration — both work well.
Zod v4's New Features
Zod v4 wasn't just a size reduction — it added meaningful new APIs that address real pain points from v3.
The pipe transform syntax cleans up chains that previously required .transform() and .refine() to be chained separately. Async validation improved significantly, making server-side validation with database lookups cleaner. The zod/mini import gives you the smallest possible Zod build for edge environments.
// 1. Pipe transforms (cleaner than .transform().refine() chains):
const CleanedString = z.string().pipe(
z.string().trim().toLowerCase()
);
// Equivalent but more readable than z.string().transform(s => s.trim().toLowerCase())
// 2. Improved async validation:
const UserSchema = z.object({
username: z.string().refine(
async (username) => {
// Check DB — now first-class in v4
const exists = await db.user.findUnique({ where: { username } });
return !exists;
},
{ message: "Username already taken" }
),
});
// Parse with async:
const result = await UserSchema.safeParseAsync(rawInput);
// 3. ZodMiniCompat — alias for smallest possible Zod v4 build:
import { z } from 'zod/mini'; // ~1.2KB — subset of Zod with most-used validators
// 4. Better .describe() for schema documentation:
const ApiKeySchema = z.string()
.describe("API key for authentication")
.min(32)
.max(64)
.regex(/^sk_/);
// Used by OpenAPI generators, JSON Schema output, LLM tool definitions
// 5. First-class JSON Schema output:
import { zodToJsonSchema } from 'zod-to-json-schema'; // v4 compatible
const jsonSchema = zodToJsonSchema(UserSchema);
// Used for: OpenAPI spec generation, form generation, LLM tool descriptions
The JSON Schema output and describe() improvements are particularly relevant in 2026 given the rise of LLM tool definitions. The Vercel AI SDK, OpenAI function calling, and Anthropic tool use all expect JSON Schema for tool parameter validation. Zod v4 makes generating those schemas from your existing validation code much cleaner.
Valibot's Unique Strengths
Despite Zod v4's improvements, Valibot retains genuine advantages in specific scenarios. The input/output type distinction is more explicit in Valibot's type system, which matters when you have transformation pipelines. The per-validator error messages require no intermediate objects. And for library authors who need to minimize peer dependency size, Valibot's truly modular design still wins.
// 1. Async validation as a first-class concept:
const UserSchema = v.objectAsync({
username: v.pipeAsync(
v.string(),
v.checkAsync(async (username) => {
const exists = await db.user.findUnique({ where: { username } });
return !exists;
}, 'Username already taken')
),
});
// 2. Transformations are explicit in the type:
// Valibot distinguishes InferInput vs InferOutput:
const ParsedDateSchema = v.pipe(
v.string(), // Input: string
v.isoDate(), // Validates ISO date format
v.transform(s => new Date(s)) // Output: Date
);
type DateInput = v.InferInput<typeof ParsedDateSchema>; // string
type DateOutput = v.InferOutput<typeof ParsedDateSchema>; // Date
// Zod has this but Valibot makes it more explicit in the type system
// 3. Custom validation actions (composable):
const noSwearWords = v.check<string>(
(value) => !containsSwearWords(value),
'This field contains inappropriate language'
);
const PostSchema = v.object({
title: v.pipe(v.string(), v.minLength(1), noSwearWords),
body: v.pipe(v.string(), v.minLength(10), noSwearWords),
// noSwearWords reused across fields without duplication
});
// 4. Smaller per-validator impact:
// Only importing what you use means unused validators = 0KB
// Ideal for: edge functions, browser-side validation in small bundles
Ecosystem Integration
This is where Zod maintains its dominant position regardless of v4 improvements. Zod is the de-facto TypeScript validation standard — every major TypeScript library added Zod support first, if not exclusively.
Zod v4 ecosystem (2026):
→ React Hook Form: @hookform/resolvers/zod ✅
→ tRPC: built-in Zod support ✅
→ Prisma: generate Zod schemas from schema ✅
→ Next.js Server Actions: native Zod integration in examples ✅
→ Fastify: fastify-type-provider-zod ✅
→ OpenAPI: zod-openapi, @asteasolutions/zod-to-openapi ✅
→ LLM tool definitions: AI SDK uses Zod natively ✅
→ Hono: @hono/zod-validator ✅
Valibot ecosystem (smaller but growing):
→ React Hook Form: @hookform/resolvers/valibot ✅
→ Modular Valibot: ✅
→ tRPC: community support ✅ (not first-class)
→ Most other integrations: ⚠ check for valibot support
The ecosystem gap is real.
Zod is the default for TypeScript validation — every library supports it.
Valibot requires checking compatibility with each tool you use.
For teams using tRPC — which has Zod deeply integrated into its router type system — switching to Valibot is a significant friction point. For teams using the AI SDK or building LLM tool definitions, Zod is the natural choice because Vercel's SDK uses Zod natively.
When to Use Which
The decision tree is clearer than most comparisons make it seem. Zod v4 is the pragmatic default for most TypeScript projects in 2026. Valibot wins in specific scenarios where bundle size truly matters at the per-byte level.
Use Zod v4 when:
→ You're integrating with tRPC, React Hook Form, Prisma, or AI SDK
→ Team already knows Zod — migration cost isn't worth it
→ Bundle size isn't a critical constraint (server-side apps, larger bundles)
→ You want maximum ecosystem support and answered questions online
→ You're defining API schemas for OpenAPI/LLM tool definitions
Use Valibot when:
→ Bundle size is critical: Cloudflare Workers, browser-side validation, CDN scripts
→ You're starting fresh with no existing validation library
→ You prefer functional composition over method chains
→ You need extremely granular control over which validators are included
→ You're building a library and want minimal peer dependencies
The honest 2026 take:
→ Zod v4's size reduction removed Valibot's biggest advantage
→ Valibot is still technically smaller per-validator
→ But Zod's ecosystem dominance is a real, practical advantage
→ New project on Next.js/tRPC? Default to Zod v4.
→ New project on edge runtime with size constraints? Evaluate Valibot seriously.
Migrating from Zod v3 to Zod v4
If you're on Zod v3, migrating to v4 is worth doing for the bundle size improvement alone. The API is largely backward compatible with a few notable changes.
The biggest behavioral change is the error handling API. ZodError.flatten() and ZodError.format() work the same way, but the internal error structure has some changes. If you're relying on specific error message formats or using the ctx.addIssue API in custom refinements, review the v4 migration guide carefully.
The z.function() API was overhauled in v4 — if you're using Zod to validate function signatures (less common), this is a breaking change. For most users who use Zod for data validation, the migration is smooth.
Run npx zod-v4-codemods if available in your project for automated migration help, and prioritize testing your error handling paths since those are where behavioral differences surface.
Performance Considerations
For server-side validation — the most common use case in Node.js applications — bundle size differences between Zod and Valibot don't matter at all. Both parse thousands of objects per second with negligible performance overhead. The CPU cost of schema validation is dominated by the actual type checking logic, not the library overhead, and both libraries are efficient.
Where library choice affects performance is startup time in serverless functions. Cold starts on AWS Lambda or Vercel Edge Functions are sensitive to bundle size — a larger bundle takes longer to download and parse. Valibot's smaller footprint can meaningfully reduce cold start times in these environments. Zod v4's ~1.8KB core is competitive for most use cases, but Valibot's per-validator pricing is still better for functions that only use a handful of validators.
For validation of very large data structures (deeply nested objects, arrays with thousands of elements), both libraries handle it well. Neither introduces significant overhead at scale.
Community and Long-Term Support
Zod's position as the de-facto TypeScript validation standard is self-reinforcing. The more libraries integrate with Zod, the more reason new projects have to choose Zod, which maintains its ecosystem lead. With 20M+ weekly downloads and integration in tRPC, Prisma, Next.js documentation examples, and the Vercel AI SDK, Zod's ecosystem position is durable.
Valibot is a younger project with an active maintainer (Fabian Hiller) and a clear technical vision. Its innovation — modular tree-shaking from the start — has influenced Zod v4's architecture. It has a smaller but engaged community and has shown consistent improvement over time.
Both libraries are MIT licensed, actively maintained, and have production users. The risk of choosing either is low from a longevity perspective.
Using Validation in Server Actions and API Routes
One underappreciated use case for validation libraries is validating incoming data in Next.js Server Actions and API routes. Since Server Actions receive FormData, you often need to parse and validate the raw string values before using them. Zod and Valibot both handle this well.
// Validating Server Action input with Zod v4:
'use server';
import { z } from 'zod';
const CreatePostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(10),
tags: z.string().transform(s => s.split(',').map(t => t.trim())),
});
export async function createPost(formData: FormData) {
const result = CreatePostSchema.safeParse(Object.fromEntries(formData));
if (!result.success) {
return { error: result.error.flatten().fieldErrors };
}
// result.data is fully typed with tags as string[]
await db.post.create({ data: result.data });
}
This pattern — parse FormData with Object.fromEntries, validate with Zod, return field errors on failure — is idiomatic in Next.js 15 and works identically with Valibot by swapping to v.safeParse(schema, Object.fromEntries(formData)).
For tRPC users, Zod is the recommended input validator for procedures. The type inference flows automatically from your Zod schema to your procedure's input type to the client's call signature — end-to-end type safety with minimal ceremony.
Choosing in 2026: The Summary
Zod v4 is the right default for most TypeScript projects. Its bundle size is now competitive, the ecosystem is unmatched, and the API is familiar to most TypeScript developers. Choose Valibot when you're writing code for the edge (sub-2MB bundle budgets) or when you're building a library and want to minimize peer dependency size. Both are excellent; you won't regret either choice.
In practice, teams rarely switch between them after the initial choice — the migration cost is low but the motivation is even lower since both solve the same problem well. Make the choice once based on your deployment targets and ecosystem requirements, then stick with it.
If you're building a library that other developers will install as a dependency, lean toward Valibot to minimize the impact on your consumers' bundle sizes. If you're building an application where you control the entire stack, Zod v4's ecosystem integration eliminates friction in every tool you'll use alongside it. The validation library decision is consequential but not irreversible — both have good migration paths if your requirements change significantly.
Compare Zod and Valibot package health at PkgPulse →
Related: Zod v4 vs ArkType vs TypeBox vs Valibot (2026) · Best TypeScript utility libraries 2026 · Browse validation packages
See the live comparison
View zod vs. valibot on PkgPulse →