Skip to main content
Back to Blog
Comparisons
3 min read
October 16, 2024

Zod vs Yup vs Valibot: Schema Validation Library Comparison

Schema validation keeps bad data out. Compare Zod (TypeScript-first), Yup (established), and Valibot (tree-shakeable) for form validation and API input parsing.

Ryel Banfield

Founder & Lead Developer

Every application needs input validation. Forms, API endpoints, environment variables, and configuration files all need data shape verification. Three libraries dominate this space, each with different design philosophies.

Philosophy

Zod: TypeScript-first schema declaration and validation. "Define a schema, infer the TypeScript type." Object-oriented API.

Yup: Battle-tested validation library inspired by Joi. Established in the React form ecosystem (Formik). Method-chaining API.

Valibot: Modular, tree-shakeable validation. Functional API where each validator is a separate import. Smallest bundle size.

Bundle Size

LibraryFull BundleTypical UsageTree-Shaken
Zod57 KB (minified)57 KBNot tree-shakeable
Yup40 KB (minified)40 KBPartially
Valibot60 KB (full)5-10 KBFully tree-shakeable

Zod's non-tree-shakeable design means you ship the full library even if you use 10% of it. Valibot's modular design means you only ship the validators you import.

API Comparison

Defining a User Schema

Zod:

import { z } from 'zod'

const userSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  age: z.number().int().min(18).max(120),
  role: z.enum(['admin', 'user', 'editor']),
})

type User = z.infer<typeof userSchema>

Yup:

import * as yup from 'yup'

const userSchema = yup.object({
  name: yup.string().required().min(2).max(50),
  email: yup.string().required().email(),
  age: yup.number().required().integer().min(18).max(120),
  role: yup.string().required().oneOf(['admin', 'user', 'editor']),
})

type User = yup.InferType<typeof userSchema>

Valibot:

import * as v from 'valibot'

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.integer(), v.minValue(18), v.maxValue(120)),
  role: v.picklist(['admin', 'user', 'editor']),
})

type User = v.InferOutput<typeof userSchema>

All three produce equivalent TypeScript types and validation behavior. The API style differs:

  • Zod: method chaining on schema objects
  • Yup: method chaining with .required() as default opt-in
  • Valibot: functional composition with pipe()

Feature Comparison

FeatureZodYupValibot
TypeScript inferenceExcellentGoodExcellent
Tree-shakingNoPartialFull
Custom error messagesYesYesYes
Async validationYesYesYes
TransformsYes (transform, preprocess)Yes (transform)Yes (transform)
Discriminated unionsYesLimitedYes
Recursive schemasYesLimitedYes
Coercionz.coerce.*Built-in castv.transform()
Form library integrationReact Hook Form, ConformFormik, React Hook FormReact Hook Form, Conform
Default values.default().default()v.optional(schema, default)
Error formatting.format(), .flatten().errors, pathsv.flatten()

Performance

Validating the same object 10,000 times:

LibraryTimeOps/Second
Valibot12ms833,000
Zod28ms357,000
Yup45ms222,000

Valibot is the fastest. Zod is 2x slower. Yup is 4x slower. For typical use (validating a form or API request), the difference is imperceptible. For high-throughput servers, it matters.

Form Integration

With React Hook Form

All three work via resolvers:

// Zod
import { zodResolver } from '@hookform/resolvers/zod'
useForm({ resolver: zodResolver(userSchema) })

// Yup
import { yupResolver } from '@hookform/resolvers/yup'
useForm({ resolver: yupResolver(userSchema) })

// Valibot
import { valibotResolver } from '@hookform/resolvers/valibot'
useForm({ resolver: valibotResolver(userSchema) })

With Server Actions (Next.js)

// Zod
const result = userSchema.safeParse(formData)

// Valibot
const result = v.safeParse(userSchema, formData)

// Yup
try {
  const result = await userSchema.validate(formData)
} catch (error) { /* handle */ }

Zod and Valibot return a result object (success/failure pattern). Yup throws on failure, requiring try/catch.

Ecosystem

MetricZodYupValibot
npm weekly downloads25M+10M+1.5M+
GitHub stars35K+22K+6K+
Community tutorialsManyManyGrowing
Form library supportBest (official resolvers)Good (Formik native)Good (resolvers)
tRPC integrationNativeThird-partyThird-party
Nuxt/SvelteKit supportGoodGoodGood

When to Choose Each

Zod

  1. New TypeScript projects (most popular, best ecosystem)
  2. tRPC users (native integration)
  3. Maximum community support (most tutorials, answers)
  4. Complex schemas (discriminated unions, recursive types)
  5. Teams prioritizing DX over bundle size

Yup

  1. Existing Formik projects (native integration)
  2. Familiar to the team (switching has a cost)
  3. Legacy codebases being maintained, not rebuilt
  4. Cast/coercion needs (Yup's casting is built-in and mature)

Valibot

  1. Bundle size matters (client-heavy apps, mobile web)
  2. Performance-critical server validation
  3. Functional programming style preference
  4. Edge/serverless where cold start size matters
  5. New projects willing to adopt a newer tool

Our Choice

We use Zod as our default. The ecosystem integration (React Hook Form, tRPC, Next.js server actions), community support, and TypeScript inference quality make it the pragmatic choice. For projects where bundle size is critical, we evaluate Valibot.

Contact us to discuss your application architecture.

ZodYupValibotvalidationTypeScriptcomparison

Ready to Start Your Project?

RCB Software builds world-class websites and applications for businesses worldwide.

Get in Touch

Related Articles