Claude
Skills
Sign in
Back

zod-patterns

Included with Lifetime
$97 forever

Zod schema validation, type-safe development, and strict TypeScript patterns. When user works with Zod, validates data, creates schemas, handles form validation, mentions z.object/z.string patterns, needs runtime validation, type-safe code, or strict TypeScript configuration.

General

What this skill does


# Zod Patterns Agent

## What's New in Zod 4 (2025)

- **Performance**: `z.array()` 7.4x faster, `z.object()` 6.5x faster (compared to Zod v3)
- **Zod Mini**: Tree-shakable functional API variant
- **Metadata API**: Attach custom metadata to schemas
- **JSON Schema**: Built-in JSON Schema generation
- **Locales**: Built-in i18n support for error messages
- **`z.looseObject()`**: Replaces `.passthrough()`
- **`z.strictObject()`**: Replaces `.strict()`

## Installation

```bash
# Standard Zod v4 (recommended)
bun add zod

# Minimal bundle for tree-shaking (1.9KB gzipped)
bun add @zod/mini
```

Requires TypeScript 5.5+ with `"strict": true` in tsconfig.

### Using @zod/mini

```typescript
// Tree-shakable imports (only includes what you use)
import { z } from "@zod/mini";

// Same API as full Zod
const UserSchema = z.object({
  id: z.string(),
  email: z.string().email(),
});

// Results in significantly smaller bundles
// Full Zod: ~5KB | @zod/mini: ~1.9KB (tree-shaken)
```

For library authors targeting v4: `import { z } from "zod/v4/core";`

## Basic Usage

### Defining and Parsing

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

// Define a schema
const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().int().positive(),
});

// Parse data (throws on invalid)
const user = UserSchema.parse({
  name: "Alice",
  email: "[email protected]",
  age: 25,
});

// Safe parse (returns result object)
const result = UserSchema.safeParse(data);
if (result.success) {
  console.log(result.data); // typed as User
} else {
  console.log(result.error.issues); // validation errors
}
```

### Type Inference

```typescript
// Infer TypeScript type from schema
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age: number }

// For schemas with transforms - input vs output types
type UserInput = z.input<typeof UserSchema>;
type UserOutput = z.output<typeof UserSchema>;
```

## Primitive Types

### Strings

```typescript
z.string(); // any string
z.string().min(1); // non-empty
z.string().max(100); // max length
z.string().length(5); // exact length
z.string().email(); // email format
z.string().url(); // URL format
z.string().uuid(); // UUID format
z.string().cuid(); // CUID format
z.string().cuid2(); // CUID2 format
z.string().ulid(); // ULID format
z.string().regex(/^[a-z]+$/); // custom regex
z.string().includes("@"); // contains substring
z.string().startsWith("http"); // starts with
z.string().endsWith(".com"); // ends with
z.string().datetime(); // ISO datetime
z.string().date(); // ISO date
z.string().time(); // ISO time
z.string().ip(); // IP address
z.string().trim(); // trim whitespace (transform)
z.string().toLowerCase(); // lowercase (transform)
z.string().toUpperCase(); // uppercase (transform)
```

### Numbers

```typescript
z.number(); // any number
z.number().int(); // integer only
z.number().positive(); // > 0
z.number().nonnegative(); // >= 0
z.number().negative(); // < 0
z.number().nonpositive(); // <= 0
z.number().min(5); // >= 5
z.number().max(100); // <= 100
z.number().gt(5); // > 5
z.number().gte(5); // >= 5 (alias for min)
z.number().lt(100); // < 100
z.number().lte(100); // <= 100 (alias for max)
z.number().multipleOf(5); // divisible by 5
z.number().finite(); // not Infinity
z.number().safe(); // within safe integer range
```

### Other Primitives

```typescript
z.boolean(); // true or false
z.bigint(); // BigInt values
z.date(); // Date objects
z.undefined(); // undefined only
z.null(); // null only
z.void(); // undefined (for function returns)
z.any(); // bypass validation
z.unknown(); // any, but type-safe usage
z.never(); // always fails
```

## Coercion

Automatically convert input types before validation:

```typescript
// String coercion
z.coerce.string(); // String(input)
z.coerce.number(); // Number(input)
z.coerce.boolean(); // Boolean(input)
z.coerce.bigint(); // BigInt(input)
z.coerce.date(); // new Date(input)

// With validation
const ageSchema = z.coerce.number().int().positive();
ageSchema.parse("25"); // 25
ageSchema.parse("abc"); // throws - NaN is not positive

// Common pitfall: empty string becomes 0
z.coerce.number().parse(""); // 0 (might not be desired)

// Fix: preprocess to handle empty strings
const safeNumber = z.preprocess(
  (val) => (val === "" ? undefined : val),
  z.coerce.number(),
);
```

## Objects

### Basic Objects

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

// By default, unknown keys are stripped
PersonSchema.parse({ name: "Bob", age: 30, extra: "ignored" });
// { name: "Bob", age: 30 }
```

### Object Modes (Zod 4)

```typescript
// Standard - strips unknown keys
z.object({ name: z.string() });

// Loose - passes through unknown keys
z.looseObject({ name: z.string() });

// Strict - rejects unknown keys
z.strictObject({ name: z.string() });
```

### Object Manipulation

```typescript
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  email: z.string().email(),
  password: z.string(),
  createdAt: z.date(),
});

// Pick specific fields
const PublicUser = UserSchema.pick({ id: true, name: true, email: true });

// Omit sensitive fields
const SafeUser = UserSchema.omit({ password: true });

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

// Make specific fields optional
const UpdateUser = UserSchema.partial({ name: true, email: true });

// Make all fields required
const RequiredUser = UserSchema.required();

// Extend with additional fields
const AdminSchema = UserSchema.extend({
  role: z.literal("admin"),
  permissions: z.array(z.string()),
});

// Merge schemas (Zod 4 - use extend instead)
const Combined = BaseSchema.extend(ExtraSchema.shape);
```

### Nested Objects

```typescript
const AddressSchema = z.object({
  street: z.string(),
  city: z.string(),
  country: z.string(),
  zip: z.string().optional(),
});

const CompanySchema = z.object({
  name: z.string(),
  address: AddressSchema,
  employees: z.array(
    z.object({
      name: z.string(),
      department: z.string(),
    }),
  ),
});
```

## Arrays and Collections

### Arrays

```typescript
z.array(z.string()); // string[]
z.array(z.string()).min(1); // at least 1 element
z.array(z.string()).max(10); // at most 10 elements
z.array(z.string()).length(5); // exactly 5 elements
z.array(z.string()).nonempty(); // same as .min(1)

// Access element schema
const arr = z.array(z.string());
arr.element; // z.string()
```

### Tuples

```typescript
// Fixed-length array with specific types
const PointSchema = z.tuple([z.number(), z.number()]);
type Point = z.infer<typeof PointSchema>; // [number, number]

// With rest elements
const ArgsSchema = z.tuple([z.string(), z.number()]).rest(z.boolean());
// [string, number, ...boolean[]]
```

### Records and Maps

```typescript
// Record<string, T>
z.record(z.string(), z.number()); // { [key: string]: number }
z.record(z.number()); // shorthand for string keys

// Map<K, V>
z.map(z.string(), z.number()); // Map<string, number>

// Set<T>
z.set(z.string()); // Set<string>
z.set(z.number()).min(1).max(10); // with size constraints
```

## Unions and Enums

### Unions

```typescript
// Basic union
const StringOrNumber = z.union([z.string(), z.number()]);
type SN = z.infer<typeof StringOrNumber>; // string | number

// Shorthand
const StringOrNumber2 = z.string().or(z.number());
```

### Discriminated Unions

```typescript
// More efficient parsing with discriminator key
const ResultSchema = z.discriminatedUnion("status", [
  z.object({
    status: z.literal("success"),
    data: z.object({ id: z.string() }),
  }),
  z.object({
    status: z.literal("error"),
    message: z.string(),
  }),
]);

type Result = z.infer<typeof ResultSchema>;
// { status: "success"; data: { id: string } } | { status: "error"; message: string }

// TypeScript narrows based on discriminator
const result = ResultSchema.parse(data);
if (result.sta

Related in General