Claude
Skills
Sign in
Back

zod

Included with Lifetime
$97 forever

TypeScript-first schema validation library with static type inference for form validation, API validation, and runtime type checking with compile-time types.

Backend & APIs

What this skill does


# Zod Validation Skill

## Summary
TypeScript-first schema validation library with static type inference. Define schemas once, get runtime validation and compile-time types automatically.

## When to Use
- Form validation with type-safe data
- API request/response validation
- Environment variable validation
- Runtime type checking with TypeScript inference
- tRPC procedure inputs/outputs
- Database schema validation (Drizzle, Prisma)

## Quick Start

```typescript
import { z } from 'zod';

// Define schema
const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  age: z.number().min(18),
  role: z.enum(['user', 'admin'])
});

// Infer TypeScript type
type User = z.infer<typeof UserSchema>;

// Validate data
const result = UserSchema.safeParse(data);
if (result.success) {
  const user: User = result.data;
}
```

<!-- SECTION: primitives -->
## Primitive Types

### Basic Types

```typescript
import { z } from 'zod';

// String with validation
const nameSchema = z.string()
  .min(2, "Too short")
  .max(50, "Too long")
  .trim();

const emailSchema = z.string().email();
const urlSchema = z.string().url();
const uuidSchema = z.string().uuid();
const regexSchema = z.string().regex(/^[A-Z]{3}$/);

// Numbers
const ageSchema = z.number()
  .int("Must be integer")
  .positive()
  .min(0)
  .max(120);

const priceSchema = z.number()
  .positive()
  .multipleOf(0.01); // Currency precision

// Boolean
const isActiveSchema = z.boolean();

// Date
const createdAtSchema = z.date()
  .min(new Date('2020-01-01'))
  .max(new Date());

const dateStringSchema = z.string().datetime(); // ISO 8601
const dateOnlySchema = z.string().date(); // YYYY-MM-DD
```

### Special Types

```typescript
// Literal values
const roleSchema = z.literal('admin');
const statusSchema = z.literal('pending');

// Enums
const ColorEnum = z.enum(['red', 'green', 'blue']);
type Color = z.infer<typeof ColorEnum>; // 'red' | 'green' | 'blue'

const NativeEnum = z.nativeEnum(MyEnum); // For TypeScript enums

// Nullable and Optional
const optionalString = z.string().optional(); // string | undefined
const nullableString = z.string().nullable(); // string | null
const nullishString = z.string().nullish(); // string | null | undefined

// Default values
const countSchema = z.number().default(0);
const settingsSchema = z.object({
  theme: z.string().default('light'),
  notifications: z.boolean().default(true)
});
```

<!-- SECTION: objects_and_arrays -->
## Objects and Arrays

### Object Schemas

```typescript
// Basic object
const UserSchema = z.object({
  id: z.string(),
  email: z.string().email(),
  name: z.string(),
  age: z.number().optional()
});

// Nested objects
const AddressSchema = z.object({
  street: z.string(),
  city: z.string(),
  country: z.string(),
  zipCode: z.string()
});

const PersonSchema = z.object({
  name: z.string(),
  address: AddressSchema,
  contacts: z.object({
    email: z.string().email(),
    phone: z.string().optional()
  })
});

// Strict vs Passthrough
const strictSchema = z.object({ name: z.string() }).strict();
// Rejects unknown keys

const passthroughSchema = z.object({ name: z.string() }).passthrough();
// Allows unknown keys

const stripSchema = z.object({ name: z.string() }).strip();
// Removes unknown keys (default)
```

### Array Schemas

```typescript
// Simple arrays
const stringArray = z.array(z.string());
const numberArray = z.array(z.number()).min(1).max(10);

// Array of objects
const UsersSchema = z.array(UserSchema);

// Non-empty arrays
const tagSchema = z.array(z.string()).nonempty("At least one tag required");

// Fixed-length arrays (tuples)
const coordinateSchema = z.tuple([z.number(), z.number()]);
type Coordinate = z.infer<typeof coordinateSchema>; // [number, number]

// Tuple with rest
const csvRowSchema = z.tuple([z.string(), z.number()]).rest(z.string());
// [string, number, ...string[]]
```

### Records and Maps

```typescript
// Record (object with dynamic keys)
const userRolesSchema = z.record(
  z.string(), // key type
  z.enum(['admin', 'user', 'guest']) // value type
);
type UserRoles = z.infer<typeof userRolesSchema>;
// { [key: string]: 'admin' | 'user' | 'guest' }

// Map
const configMapSchema = z.map(
  z.string(), // key
  z.number()  // value
);

// Set
const uniqueTagsSchema = z.set(z.string());
```

<!-- SECTION: type_inference -->
## Type Inference

```typescript
import { z } from 'zod';

// Infer output type
const UserSchema = z.object({
  id: z.string(),
  email: z.string().email(),
  age: z.number()
});

type User = z.infer<typeof UserSchema>;
// { id: string; email: string; age: number }

// Infer input type (before transforms)
const TransformSchema = z.object({
  date: z.string().transform(s => new Date(s))
});

type Input = z.input<typeof TransformSchema>;
// { date: string }

type Output = z.output<typeof TransformSchema>;
// { date: Date }

// Using inferred types in functions
function createUser(data: User): void {
  // data is type-safe
}

function validateAndCreate(data: unknown): User | null {
  const result = UserSchema.safeParse(data);
  return result.success ? result.data : null;
}
```

<!-- SECTION: validation_methods -->
## Validation Methods

### Parse vs SafeParse

```typescript
// parse() - Throws on failure
try {
  const user = UserSchema.parse(data);
  // user is type User
} catch (error) {
  if (error instanceof z.ZodError) {
    console.error(error.issues);
  }
}

// safeParse() - Returns result object
const result = UserSchema.safeParse(data);

if (result.success) {
  const user = result.data; // type User
} else {
  const errors = result.error.issues;
  errors.forEach(err => {
    console.log(`${err.path}: ${err.message}`);
  });
}

// parseAsync() - For async refinements
const asyncResult = await UserSchema.parseAsync(data);

// safeParseAsync() - Safe async version
const asyncSafeResult = await UserSchema.safeParseAsync(data);
```

### Partial Validation

```typescript
// Check if data matches schema without throwing
const isValid = UserSchema.safeParse(data).success;

// Custom type guards
function isUser(data: unknown): data is User {
  return UserSchema.safeParse(data).success;
}

if (isUser(unknownData)) {
  // TypeScript knows unknownData is User
  console.log(unknownData.email);
}
```

<!-- SECTION: schema_composition -->
## Schema Composition

### Extending and Merging

```typescript
// Extend (add fields)
const BaseUserSchema = z.object({
  id: z.string(),
  email: z.string()
});

const AdminUserSchema = BaseUserSchema.extend({
  role: z.literal('admin'),
  permissions: z.array(z.string())
});

// Merge (combine schemas)
const NameSchema = z.object({ name: z.string() });
const AgeSchema = z.object({ age: z.number() });

const PersonSchema = NameSchema.merge(AgeSchema);
// { name: string; age: number }

// Pick (select fields)
const UserIdEmail = UserSchema.pick({ id: true, email: true });

// Omit (exclude fields)
const UserWithoutId = UserSchema.omit({ id: true });

// Partial (make all fields optional)
const PartialUser = UserSchema.partial();

// DeepPartial (recursive partial)
const DeepPartialUser = UserSchema.deepPartial();

// Required (make all fields required)
const RequiredUser = UserSchema.required();
```

### Union and Intersection

```typescript
// Union (OR)
const StringOrNumber = z.union([z.string(), z.number()]);
// Shorthand
const StringOrNumberAlt = z.string().or(z.number());

// Discriminated Union (tagged union)
const SuccessResponse = z.object({
  status: z.literal('success'),
  data: z.any()
});

const ErrorResponse = z.object({
  status: z.literal('error'),
  message: z.string()
});

const ApiResponse = z.discriminatedUnion('status', [
  SuccessResponse,
  ErrorResponse
]);

// Intersection (AND)
const User = z.object({ name: z.string() });
const Timestamps = z.object({
  createdAt: z.date(),
  updatedAt: z.date()
});

const UserWithTimestamps = z.intersection(User, Timestamps);
// Shorthand
const UserWithTimestampsAlt = User.and(T

Related in Backend & APIs