Skip to main content

Zod v4 vs ArkType vs TypeBox vs Valibot

·PkgPulse Team
0

TL;DR

Zod v4 remains the default for TypeScript validation — but Valibot (8KB vs Zod's 60KB) and ArkType (fastest runtime parsing) are compelling for performance-critical use cases. TypeBox generates JSON Schema natively, making it the best choice for OpenAPI/Swagger integration. For new projects: Zod v4. For edge/bundle-size-critical: Valibot. For OpenAPI: TypeBox. For maximum runtime speed: ArkType.

Key Takeaways

  • Zod v4: 60KB, 10M+ downloads/week, best ecosystem (react-hook-form, trpc, drizzle)
  • Valibot: 8KB, tree-shakable, modular API, ~10x smaller than Zod
  • ArkType: Fastest parser (3-10x faster than Zod), TypeScript syntax strings
  • TypeBox: JSON Schema native, Static<typeof Schema> TypeScript types
  • Performance: ArkType > Valibot > TypeBox > Zod (but all are "fast enough" for most apps)
  • Ecosystem: Zod integrates with everything; others are catching up

Downloads

PackageWeekly DownloadsTrend
zod~10M↑ Growing
@sinclair/typebox~6M↑ Growing
valibot~1M↑ Fast growing
arktype~200K↑ Growing

Performance Benchmarks

Schema: User object with 10 fields, nested address, array of tags

Parsing 100,000 objects:
  ArkType:      45ms   ← Fastest
  Valibot:      120ms
  TypeBox:      180ms
  Zod v4:       280ms  (v4 is 2x faster than v3's ~580ms)

Bundle size (minified + gzipped):
  Valibot:      8KB    ← Smallest
  ArkType:      12KB
  TypeBox:      60KB   (includes JSON Schema types)
  Zod v4:       60KB

Type inference speed (tsc, 50-field schema):
  ArkType:      ~200ms
  Zod v4:       ~450ms
  Valibot:      ~600ms
  TypeBox:      ~300ms

Zod v4: The Default

// Zod v4 — new features and performance improvements:
import { z } from 'zod';

// Basic schema (same as v3):
const UserSchema = z.object({
  id: z.string().cuid2(),
  email: z.string().email(),
  name: z.string().min(2).max(100),
  age: z.number().int().min(0).max(150).optional(),
  role: z.enum(['user', 'admin', 'moderator']),
  tags: z.array(z.string()).max(10),
  address: z.object({
    street: z.string(),
    city: z.string(),
    country: z.string().length(2),  // ISO 2-letter
  }).optional(),
  metadata: z.record(z.string(), z.unknown()),
  createdAt: z.coerce.date(),  // Auto-coerce string → Date
});

type User = z.infer<typeof UserSchema>;

// Zod v4 new: z.file() for Blob/File
const UploadSchema = z.object({
  file: z.instanceof(File)
    .refine(f => f.size < 5_000_000, 'Max 5MB')
    .refine(f => ['image/jpeg', 'image/png', 'image/webp'].includes(f.type), 'Must be JPEG/PNG/WebP'),
  caption: z.string().max(500).optional(),
});

// Zod v4 new: z.pipe() for chained transforms
const ParsedDateSchema = z
  .string()
  .pipe(z.coerce.date());  // string → validated Date

// Zod v4 new: z.toJSONSchema()
const jsonSchema = z.toJSONSchema(UserSchema);
// Generates standard JSON Schema — useful for OpenAPI docs

// Error formatting (v4 — cleaner):
const result = UserSchema.safeParse({ email: 'bad' });
if (!result.success) {
  const errors = result.error.flatten();
  // { fieldErrors: { email: ['Invalid email'] }, formErrors: [] }
}

Valibot: Bundle-Size Champion

// Valibot — modular, tree-shakable:
import {
  object, string, number, array, optional, enum_,
  email, minLength, maxLength, integer, minValue, maxValue,
  parse, safeParse, flatten,
  type InferInput, type InferOutput,
} from 'valibot';

// Only imports what you use — tree-shaking reduces bundle to ~2-5KB for simple schemas
const UserSchema = object({
  id: string([minLength(1)]),
  email: string([email()]),
  name: string([minLength(2), maxLength(100)]),
  age: optional(number([integer(), minValue(0), maxValue(150)])),
  role: enum_(['user', 'admin', 'moderator']),
  tags: array(string(), [maxLength(10)]),
});

type User = InferInput<typeof UserSchema>;

// Parse (throws on error):
const user = parse(UserSchema, rawData);

// Safe parse (returns result/error):
const result = safeParse(UserSchema, rawData);
if (result.success) {
  console.log(result.output);
} else {
  const errors = flatten(result.issues);
  // { nested: { email: ['Invalid email'] } }
}
// Valibot with React Hook Form:
import { valibotResolver } from '@hookform/resolvers/valibot';
import { useForm } from 'react-hook-form';

function SignupForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: valibotResolver(UserSchema),
  });
  
  return (
    <form onSubmit={handleSubmit(console.log)}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}
    </form>
  );
}

ArkType: Fastest Runtime + TypeScript Syntax

// ArkType — TypeScript-syntax strings for schemas:
import { type } from 'arktype';

// Syntax feels like writing TypeScript:
const User = type({
  id: 'string',
  email: 'string.email',
  name: '2 <= string <= 100',   // min/max length shorthand!
  age: 'number.integer | undefined',
  role: '"user" | "admin" | "moderator"',
  tags: 'string[] <= 10',       // array with max length
  createdAt: 'Date',
});

type User = typeof User.infer;

// Parse:
const result = User(rawData);

// ArkType returns morph (with parse) or error:
if (result instanceof type.errors) {
  console.log(result.summary);  // Human-readable error
} else {
  // result is User
}

// ArkType advanced: morphs (transform)
const ParsedDate = type('string').pipe(s => new Date(s), 'Date');

// Recursive types (Zod struggles here):
const TreeNode = type({
  value: 'number',
  children: 'TreeNode[]',  // Self-referencing!
}).describe('TreeNode');  // Named for error messages

TypeBox: JSON Schema Native

// TypeBox — generates JSON Schema, used in Fastify/Hono:
import { Type, Static } from '@sinclair/typebox';
import { Value } from '@sinclair/typebox/value';

// TypeBox schema IS JSON Schema:
const UserSchema = Type.Object({
  id: Type.String({ format: 'uuid' }),
  email: Type.String({ format: 'email' }),
  name: Type.String({ minLength: 2, maxLength: 100 }),
  age: Type.Optional(Type.Integer({ minimum: 0, maximum: 150 })),
  role: Type.Union([
    Type.Literal('user'),
    Type.Literal('admin'),
    Type.Literal('moderator'),
  ]),
  tags: Type.Array(Type.String(), { maxItems: 10 }),
});

// TypeScript type from schema:
type User = Static<typeof UserSchema>;

// Validate:
const result = Value.Check(UserSchema, rawData);
if (!result) {
  const errors = [...Value.Errors(UserSchema, rawData)];
  // [{ path: '/email', message: 'Expected string' }]
}

// TypeBox + Hono (validated routes with OpenAPI):
import { Hono } from 'hono';
import { describeRoute } from 'hono-openapi';

const app = new Hono();
app.post('/users',
  describeRoute({
    requestBody: { content: { 'application/json': { schema: UserSchema } } },
    responses: { 201: { description: 'Created' } },
  }),
  async (c) => { /* handler */ }
);

// Export OpenAPI spec:
// app.doc('/openapi.json', { openapi: '3.0.0', info: { title: 'API', version: '1' } })

Decision Guide

Use Zod v4 if:
  → Default choice — best ecosystem (react-hook-form, trpc, drizzle, next-safe-action)
  → Team already knows Zod v3 (v4 is mostly backwards compatible)
  → Need broad library compatibility
  → Bundle size is not a constraint

Use Valibot if:
  → Edge runtime / bundle size critical (<5KB budget)
  → Want tree-shakable, pay-only-for-what-you-use
  → Cloudflare Workers or similar constrained environments

Use ArkType if:
  → Parsing millions of objects (backend hot path)
  → Love TypeScript-native syntax strings
  → Need recursive types easily
  → Fastest possible validation

Use TypeBox if:
  → Building OpenAPI/Swagger documentation
  → Using Fastify (TypeBox is Fastify's native schema)
  → Need JSON Schema output for other tools
  → API validation that also generates docs

Compare Zod, Valibot, ArkType, and TypeBox on PkgPulse.

Comments

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.