Valibot vs Zod: Bundle Size and Tree-Shaking

Photo by Nikowsk via Wikimedia Commons (CC BY-SA 4.0)
Yes, and by a large margin for typical schemas. Valibot v1 lands around 1.3 kB gzipped because each schema and validation is a separate tree-shakeable function. Classic Zod v4 is roughly 13 kB because its method-chaining API bundles everything into one object, and even the newer zod/v4-mini export is about 3.9 kB.
Zod's classic API chains methods off a single object, so every validator lives on the same class. A bundler cannot prove you never call a given method just because it does not appear in your code, so it keeps the whole runtime. Valibot avoids this by making every validation a standalone exported function.
No. Writing an import-star-as-v statement looks like it imports everything, but modern bundlers such as esbuild, Rollup, and Rolldown analyse which named exports you actually reference and drop the rest. The namespace import is purely an ergonomic convenience with no bundle-size penalty.
Usually not, unless bundle size is a hard, measured constraint like an edge-function size limit. The refactor cost, the loss of Zod-specific adapters, and the risk of subtle validation differences rarely pay off on the server, where bundle size is irrelevant. Measure first, then decide.
zod/v4-mini is a tree-shakeable export introduced in Zod v4 that replaces method chaining with standalone check functions, bringing the gzipped cost down to roughly 3.9 kB. Use it when you want to stay in the Zod ecosystem but need a smaller bundle for the browser or edge. It is more verbose than classic Zod but still larger than Valibot.

Photo by Nikowsk via Wikimedia Commons (CC BY-SA 4.0)
Key Takeaway
For bundle-sensitive frontends and edge functions, Valibot beats Zod on size: Valibot v1 ships around 1.3 kB gzipped because its schema and validation functions are separate, tree-shakeable modules. Zod v4 standard bundles everything at roughly 13 kB; the new zod/v4-mini export trims that to about 3.9 kB, but Valibot's modular pipe design still wins.
I reach for Zod on almost every TypeScript project I run. It is the default for a reason: the API is comfortable, the type inference is excellent, and the ecosystem around it is enormous. But on a recent project I was shipping validation into a Cloudflare Worker and watching the bundle budget like a hawk, and Zod's weight became a real problem. That is when I started taking Valibot seriously, and the difference is not marginal.
The core question is not which library validates better. Both are correct, both infer types cleanly, both cover the same shapes. The question is what ends up in your production bundle, and that comes down to a single architectural decision each library made about how you compose a schema.
Zod's classic API is method chaining on a single object. You import one thing and call methods off it. That ergonomic API is exactly what makes it hard to shrink. When you write the import below, the bundler pulls in the whole class graph because every method lives on the same object; a bundler cannot prove that you never call the string date parser or the discriminated union just because you did not type it here.
// Zod classic — one import, everything comes with it
import { z } from "zod";
const LoginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type LoginData = z.infer<typeof LoginSchema>;
// The whole Zod runtime (~13 kB gzipped) is now in your bundle,
// even though this schema uses maybe five features.This is not a flaw so much as a trade-off. Method chaining reads beautifully and it is the reason so many of us fell in love with Zod. But the object that carries all those methods is one indivisible unit as far as the bundler is concerned.
Valibot inverts the design. Instead of methods on a class, every schema and every validation is a standalone function you import by name. Constraints are composed with a pipe function that threads a value through a sequence of actions. Because each action is its own exported function, the bundler keeps only the functions you actually reference and drops the rest. This is textbook tree-shaking, and it is why Valibot v1 lands around 1.3 kB gzipped for a typical schema.
// Valibot — import only the functions you name
import * as v from "valibot";
const LoginSchema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
});
type LoginData = v.InferOutput<typeof LoginSchema>;
// Parse the same way you would with Zod
const result = v.parse(LoginSchema, { email: "[email protected]", password: "secret12" });The wildcard import (import star as v) looks like it pulls in everything, but it does not. Modern bundlers like esbuild, Rollup, and Rolldown resolve which named exports you actually use and shake out the rest. The namespace import is just an ergonomic convenience, not a bundle-size cost.
Zod's maintainer clearly heard the bundle-size complaints. Zod v4, which landed in early 2025, introduced a separate zod/v4-mini export that abandons method chaining in favour of standalone check functions, much closer to Valibot's style. It brings the gzipped cost down to roughly 3.9 kB, a big improvement over the ~13 kB standard build. The catch is that the API is noticeably more verbose than classic Zod, and you give up some of the chaining ergonomics that made Zod pleasant in the first place.
// zod/v4-mini — standalone checks, no method chaining
import * as z from "zod/mini";
const LoginSchema = z.object({
email: z.string().check(z.email()),
password: z.string().check(z.minLength(8)),
});
type LoginData = z.infer<typeof LoginSchema>;
// ~3.9 kB gzipped — smaller than classic Zod,
// still roughly 3x heavier than Valibot for the same schema.| Dimension | Valibot v1 | Zod v4 (classic / mini) |
|---|---|---|
| Gzipped size (typical schema) | ~1.3 kB | ~13 kB classic / ~3.9 kB mini |
| Composition model | Standalone functions + pipe() | Method chaining / .check() in mini |
| Tree-shakeable | Yes, by design | No (classic) / Yes (mini) |
| API ergonomics | Explicit, more imports | Very fluent (classic) / verbose (mini) |
| Ecosystem & adapters | Growing, good but smaller | Huge, de facto standard |
| Best fit | Edge, browser, size-critical | Node servers, existing Zod code |
Do not migrate a large existing Zod codebase to Valibot just to save kilobytes on the server. The refactor cost, the loss of Zod-specific adapters, and the risk of subtle validation differences almost never pay off unless bundle size is a hard, measured constraint. Measure first, then decide.
The honest summary: Valibot won the bundle-size argument so convincingly that Zod restructured its own API to answer it. That is the strongest endorsement Valibot could get. But Zod is not standing still, and for most server work its ergonomics and ecosystem still make it my default. Choose based on where the code runs, not on which library is trendier this quarter.