DEV Community

Alex Spinov
Alex Spinov

Posted on

Valibot Has a Free Schema Library That Is 98% Smaller Than Zod

Zod is the standard for TypeScript validation. But it ships 14KB to the browser. Valibot does the same thing in less than 1KB thanks to tree-shaking.

The Size Difference

Library Full bundle After tree-shaking (basic schema)
Zod 14.3KB 14.3KB (no tree-shaking)
Valibot 8.7KB 0.5-1KB (fully tree-shakeable)

Zod uses a class-based API (methods on objects). Classes cannot be tree-shaken. Valibot uses functions — bundlers remove what you do not use.

Basic Validation

import * as v from "valibot";

// Define schema
const UserSchema = v.object({
  name: v.pipe(v.string(), v.minLength(2), v.maxLength(50)),
  email: v.pipe(v.string(), v.email()),
  age: v.pipe(v.number(), v.minValue(0), v.maxValue(150)),
  role: v.picklist(["admin", "user", "editor"]),
  bio: v.optional(v.pipe(v.string(), v.maxLength(500))),
});

// Infer TypeScript type
type User = v.InferOutput<typeof UserSchema>;
// { name: string; email: string; age: number; role: "admin" | "user" | "editor"; bio?: string }

// Validate
const result = v.safeParse(UserSchema, input);
if (result.success) {
  console.log(result.output); // Typed as User
} else {
  console.log(result.issues); // Detailed error messages
}
Enter fullscreen mode Exit fullscreen mode

Zod vs Valibot Comparison

// Zod
const schema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  age: z.number().min(0),
});

// Valibot — very similar!
const schema = v.object({
  name: v.pipe(v.string(), v.minLength(2), v.maxLength(50)),
  email: v.pipe(v.string(), v.email()),
  age: v.pipe(v.number(), v.minValue(0)),
});
Enter fullscreen mode Exit fullscreen mode

The main difference: Valibot uses pipe() for chaining validations instead of method chaining.

Advanced Schemas

Unions and Variants

const EventSchema = v.variant("type", [
  v.object({ type: v.literal("click"), x: v.number(), y: v.number() }),
  v.object({ type: v.literal("keypress"), key: v.string() }),
  v.object({ type: v.literal("scroll"), deltaY: v.number() }),
]);
Enter fullscreen mode Exit fullscreen mode

Transformations

const DateSchema = v.pipe(
  v.string(),
  v.isoTimestamp(),
  v.transform((input) => new Date(input)),
);
// Input: string → Output: Date
Enter fullscreen mode Exit fullscreen mode

Recursive Types

type Comment = { text: string; replies: Comment[] };

const CommentSchema: v.GenericSchema<Comment> = v.object({
  text: v.string(),
  replies: v.lazy(() => v.array(CommentSchema)),
});
Enter fullscreen mode Exit fullscreen mode

Framework Integration

React Hook Form

import { useForm } from "react-hook-form";
import { valibotResolver } from "@hookform/resolvers/valibot";

const form = useForm({
  resolver: valibotResolver(UserSchema),
});
Enter fullscreen mode Exit fullscreen mode

tRPC

const router = t.router({
  createUser: t.procedure
    .input(v.parser(UserSchema))
    .mutation(({ input }) => { /* input is typed */ }),
});
Enter fullscreen mode Exit fullscreen mode

When to Use Valibot vs Zod

Choose Valibot when:

  • Bundle size matters (frontend, edge, mobile)
  • You only use a few validations (tree-shaking saves most)
  • Performance is critical (Valibot is faster in benchmarks)

Choose Zod when:

  • Ecosystem integration (more libraries support Zod directly)
  • Team familiarity
  • Method chaining preference (.string().email() vs pipe(string(), email()))

Building type-safe applications? I create developer tools and data solutions. Email spinov001@gmail.com or explore my Apify tools.

Top comments (0)