Skip to main content

Guide

Valibot vs Zod v4: Best TypeScript Validator 2026

Zod v4 ships Zod Mini for tree-shaking. Valibot is still 90% smaller. We compare bundle size, runtime speed, API design, and ecosystem fit for 2026 now.

·PkgPulse Team·
0

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

ValibotZod v4Zod Mini (v4)
Bundle (login form)1.37 KB17.7 KB6.88 KB
Bundle (complex schema)~3 KB~25 KB~12 KB
ArchitectureModular functionsMethod chainingTree-shakeable
TypeScript inference
Runtime performanceFastFaster (v4)Faster (v4)
tRPC integration✅ (adapter)✅ Native✅ Native
Standard Schema
Ecosystem sizeGrowingLargeLarge
Learning curveModerateLowLow
Weekly downloads~4M~12MIncluded 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

Migrating Between Valibot and Zod

Because both libraries implement the Standard Schema spec, migrating between them is largely a mechanical API translation exercise rather than an architectural rethink. The types you export (LoginSchema, User, etc.) remain the same; only the schema definition syntax changes.

The most common migration direction is Zod → Valibot for teams that deploy to edge runtimes and discover Zod's bundle size is contributing meaningfully to cold start latency. The @valibot/to-zod and community migration guides cover the common patterns, but a few idioms require attention. Zod's .transform() method, which chains transformations as part of the schema definition, maps to Valibot's transform() validator inside a pipe(). Zod's .superRefine() for complex cross-field validation maps to Valibot's check() function. Zod's .default() maps to Valibot's optional(schema, defaultValue) call signature.

The reverse migration (Valibot → Zod) is less common but happens when a team adopts a framework with deep native Zod integration — tRPC being the primary example. tRPC v10 and v11 ship first-class Zod inference for procedure inputs and outputs; the type inference is built directly into tRPC's internals. While Standard Schema compatibility means Valibot schemas technically work with tRPC, the TypeScript ergonomics are smoother with Zod because tRPC was designed around Zod's specific type representation.

Validation in Edge Functions: A Practical Example

The bundle size difference between Valibot and Zod becomes concrete in the context of a Cloudflare Worker that validates incoming requests. Cloudflare Workers have a per-request CPU time limit and bundle size constraints — large bundles increase worker initialization time and count against the compressed script size limit.

A worker that imports Valibot for request validation adds approximately 1.4KB to the bundle for a typical schema. The same worker importing Zod standard adds approximately 17.7KB. For a worker bundle already at, say, 40KB, adding Valibot increases it by 3.5% while adding Zod increases it by 44%. At the Cloudflare Workers free tier's 1MB compressed script size limit, this headroom matters when a worker composes multiple validation schemas, utility libraries, and business logic.

Zod Mini closes the gap to approximately 6.9KB for the same schema — still 5x larger than Valibot, but the key consideration is API compatibility. Teams already using Zod who are targeting edge runtimes can switch imports from import { z } from "zod" to import { object, string } from "zod/v4-mini" and get meaningful bundle reduction without changing any of their framework integrations, resolver types, or ecosystem tooling. This makes Zod Mini the pragmatic upgrade path for Zod users who feel bundle pressure without wanting to migrate to a different library's API.

Compare Valibot and Zod download trends at PkgPulse.

Related: Zod vs ArkType 2026 · Zod vs Yup 2026 · Zod vs TypeBox 2026

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.