Skip to main content

Valibot vs Zod v4: Best TypeScript Validator 2026

·PkgPulse Team

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

Compare Valibot and Zod download trends at PkgPulse.

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

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.