DEV Community

Alex Spinov
Alex Spinov

Posted on

Zod Has a Free TypeScript Schema Validation Library — No Codegen Needed

Zod lets you define schemas once and get TypeScript types, runtime validation, and error messages automatically.

The Validation Problem

TypeScript types disappear at runtime. Your API receives any. Your form data is unknown. You write validation logic AND types — and keep them in sync manually.

Zod: define the schema once, get both the type and the validator.

What You Get for Free

Schema = Type + Validator:

import { z } from 'zod';

const UserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().positive().optional(),
});

// Extract TypeScript type automatically
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age?: number }

// Runtime validation
const result = UserSchema.safeParse(unknownData);
if (result.success) {
  console.log(result.data); // fully typed as User
} else {
  console.log(result.error.issues); // detailed error messages
}
Enter fullscreen mode Exit fullscreen mode

Why Zod Won

Zero dependencies — 0 external deps, ~13KB gzipped
Works everywhere — browser, Node, Deno, Bun, edge functions
Composable schemas.extend(), .merge(), .pick(), .omit(), .partial()
Transforms — parse and transform in one step:

const DateSchema = z.string().transform((s) => new Date(s));
// Input: string, Output: Date
Enter fullscreen mode Exit fullscreen mode

Ecosystem integrations:

  • tRPC — uses Zod for input validation
  • React Hook Form@hookform/resolvers/zod
  • Next.js Server Actions — validate form submissions
  • Hono — route validation middleware
  • Astro — content collection schemas

Advanced Patterns

// Discriminated unions
const Shape = z.discriminatedUnion('type', [
  z.object({ type: z.literal('circle'), radius: z.number() }),
  z.object({ type: z.literal('square'), side: z.number() }),
]);

// Recursive types
const Category: z.ZodType<Category> = z.object({
  name: z.string(),
  children: z.lazy(() => Category.array()),
});

// Branded types (nominal typing)
const UserId = z.string().uuid().brand<'UserId'>();
type UserId = z.infer<typeof UserId>;
Enter fullscreen mode Exit fullscreen mode

Quick Start

npm install zod
Enter fullscreen mode Exit fullscreen mode

One dependency. No config. No codegen. Start validating.

If you're writing TypeScript and NOT using Zod — you're maintaining duplicate type definitions.


Need web scraping or data extraction? Check out my tools on Apify — get structured data from any website in minutes.

Custom solution? Email spinov001@gmail.com — quote in 2 hours.

Top comments (0)