zod v4

Zod v4 - Schema Validation Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "zod v4" with this command: npx skills add slanycukr/riot-api-project/slanycukr-riot-api-project-zod-v4

Zod v4 - Schema Validation Skill

Zod v4 is a TypeScript-first schema declaration and validation library that provides static type inference, runtime validation, and exceptional performance. Version 4 delivers 14x faster parsing and 66% smaller bundles while maintaining full type safety.

Quick Start

npm install zod@^4.0.0

import * as z from "zod";

// Basic schema creation and validation const UserSchema = z.object({ id: z.number().int().positive(), email: z.email(), username: z.string().min(3).max(20), age: z.number().int().min(18).optional(), });

type User = z.infer<typeof UserSchema>;

// Parse and validate const user = UserSchema.parse({ id: 1, email: "user@example.com", username: "johndoe", age: 25, });

// Safe parsing with error handling const result = UserSchema.safeParse(input); if (!result.success) { console.log(result.error); }

Common Patterns

Object Schemas with Validation

// Comprehensive user validation const UserProfile = z.object({ id: z.number().int().positive(), email: z.email(), username: z .string() .min(3, "Username must be at least 3 characters") .max(20, "Username cannot exceed 20 characters") .regex(/^[a-zA-Z0-9_]+$/, "Only alphanumeric characters and underscores"), age: z.number().int().min(13).max(120), role: z.enum(["user", "admin", "moderator"]).default("user"), bio: z.string().max(500).optional(), website: z.url().optional(), createdAt: z.date().default(() => new Date()), });

// Extending schemas const AdminProfile = UserProfile.extend({ permissions: z.array(z.string()), accessLevel: z.number().min(1).max(10), });

String Format Validation

// Built-in format validators const Validations = { email: z.email(), uuid: z.uuidv4(), url: z.url(), ipv4: z.ipv4(), ipv6: z.ipv6(), base64: z.base64(), jwt: z.jwt(),

// ISO formats isoDate: z.iso.date(), isoDateTime: z.iso.datetime(), isoTime: z.iso.time(),

// Custom email with specific pattern strictEmail: z.email({ pattern: z.regexes.rfc5322Email }), };

// Template literal validation const VersionString = z.templateLiteral([ z.number(), ".", z.number(), ".", z.number(), ]);

const CSSValue = z.templateLiteral([ z.number(), z.enum(["px", "em", "rem", "%", "vh", "vw"]), ]);

Array and Collection Validation

// Array with constraints const NumberArray = z.array(z.number()).min(1).max(100);

// Tuple validation const Coordinates = z.tuple([z.number(), z.number()]); const MixedTuple = z.tuple([z.string(), z.number()], z.boolean());

// Set validation const UniqueStrings = z.set(z.string()).min(3);

// Map validation const UserPermissions = z.map( z.string(), // user ID z.array(z.string()), // permissions );

// Record validation const StringToNumber = z.record(z.string(), z.number()); const StatusRecord = z.record( z.enum(["pending", "active", "complete"]), z.boolean(), );

Union and Discriminated Unions

// Simple union const StringOrNumber = z.union([z.string(), z.number()]);

// Discriminated union for API responses const ApiResponse = z.discriminatedUnion("status", [ z.object({ status: z.literal("success"), data: z.unknown(), }), z.object({ status: z.literal("error"), error: z.string(), code: z.number(), }), z.object({ status: z.literal("loading"), message: z.string().optional(), }), ]);

// Nested discriminated unions const BaseError = z.object({ status: z.literal("error"), message: z.string(), });

const DetailedError = z.discriminatedUnion("code", [ BaseError.extend({ code: z.literal(400), field: z.string() }), BaseError.extend({ code: z.literal(401), realm: z.string() }), BaseError.extend({ code: z.literal(500), stack: z.string() }), ]);

Custom Refinements and Validation

// Custom validation with refinements const PasswordSchema = z .string() .min(8, "Password must be at least 8 characters") .refine((val) => /[A-Z]/.test(val), "Must contain uppercase letter") .refine((val) => /[a-z]/.test(val), "Must contain lowercase letter") .refine((val) => /[0-9]/.test(val), "Must contain number") .refine((val) => /[^A-Za-z0-9]/.test(val), "Must contain special character");

// Complex validation with superRefine const UserRegistration = z .object({ email: z.email(), password: z.string(), confirmPassword: z.string(), age: z.number(), termsAccepted: z.boolean(), }) .superRefine((data, ctx) => { if (data.password !== data.confirmPassword) { ctx.addIssue({ code: "custom", path: ["confirmPassword"], message: "Passwords must match", }); }

if (data.age &#x3C; 18 &#x26;&#x26; !data.termsAccepted) {
  ctx.addIssue({
    code: "custom",
    path: ["termsAccepted"],
    message: "Parental consent required for users under 18",
  });
}

});

Transformations and Data Processing

// Transform data during validation const StringToNumber = z .string() .transform((val) => parseInt(val, 10)) .refine((val) => !isNaN(val), "Must be a valid number");

const TimestampToDate = z .number() .transform((timestamp) => new Date(timestamp));

const NormalizeEmail = z.string().transform((val) => val.toLowerCase().trim());

// Overwrite for type-preserving transforms const RoundNumber = z.number().overwrite((val) => Math.round(val));

// Pipeline transformations const ProcessUrl = z .string() .transform((val) => val.trim()) .transform((val) => val.toLowerCase()) .transform((val) => { try { return new URL(val); } catch { throw new Error("Invalid URL format"); } });

Recursive Schemas

// Recursive category structure const Category = z.object({ id: z.number(), name: z.string(), get subcategories() { return z.array(Category); }, });

// Mutually recursive types const User = z.object({ id: z.number(), name: z.string(), get posts() { return z.array(Post); }, });

const Post = z.object({ id: z.number(), title: z.string(), content: z.string(), get author() { return User; }, get comments() { return z.array(Comment); }, });

const Comment = z.object({ id: z.number(), text: z.string(), get author() { return User.pick({ id: true, name: true }); }, });

File Validation

// File upload validation const ImageUpload = z.object({ avatar: z .file() .max(5_000_000, "File must be less than 5MB") .mime(["image/jpeg", "image/png", "image/webp"], "Must be an image"),

banner: z .file() .max(10_000_000, "File must be less than 10MB") .mime(["image/jpeg", "image/png"], "Must be JPEG or PNG") .optional(), });

// Document validation const DocumentUpload = z .file() .min(1000, "File must be at least 1KB") .max(50_000_000, "File must be less than 50MB") .mime([ "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ]);

Function Validation

// Define validated functions const CalculateTax = z.function({ input: [z.number().min(0), z.number().min(0).max(1)], output: z.number(), });

const taxCalculator = CalculateTax.implement((amount, rate) => { return amount * rate; });

// Async function validation const FetchUser = z.function({ input: [z.number().int().positive()], output: z.object({ id: z.number(), name: z.string(), email: z.email(), }), });

const getUser = FetchUser.implementAsync(async (id) => { const response = await fetch(/api/users/${id}); return response.json(); });

Error Handling

// Custom error messages const CustomValidation = z.object({ username: z .string({ error: (issue) => { if (issue.input === undefined) return "Username is required"; if (typeof issue.input !== "string") return "Username must be a string"; return "Invalid username"; }, }) .min(3, "Username must be at least 3 characters"), });

// Error handling patterns const validateInput = (input: unknown) => { const result = UserSchema.safeParse(input);

if (!result.success) { const error = result.error;

// Pretty print errors
console.log(z.prettifyError(error));

// Extract field errors
const fieldErrors = error.issues.reduce(
  (acc, issue) => {
    const field = issue.path.join(".");
    acc[field] = issue.message;
    return acc;
  },
  {} as Record&#x3C;string, string>,
);

return { success: false, errors: fieldErrors };

}

return { success: true, data: result.data }; };

Default Values and Coercion

// Schema with defaults const ConfigSchema = z.object({ theme: z.enum(["light", "dark"]).default("light"), notifications: z.boolean().default(true), fontSize: z.number().min(10).max(30).default(14), timeout: z.number().default(5000), });

// Type coercion const CoercedConfig = z.object({ port: z.coerce.number().default(3000), https: z.coerce.boolean().default(false), maxConnections: z.coerce.number().int().positive().default(100), });

// Environment variable parsing const EnvSchema = z.object({ NODE_ENV: z .enum(["development", "production", "test"]) .default("development"), PORT: z.coerce.number().default(3000), DEBUG: z.stringbool().default("false"), });

Zod Mini (Tree-Shakable)

import * as z from "zod/mini";

// Functional API for smaller bundles const OptionalString = z.optional(z.string()); const StringArray = z.array(z.string()); const StringOrNumber = z.union([z.string(), z.number()]);

// Check functions for validations const ValidatedEmail = z .string() .check(z.regex(/@/), z.minLength(5), z.maxLength(100));

const PositiveInt = z.number().check(z.int(), z.positive(), z.lt(1000));

const NonEmptyArray = z.array(z.any()).check(z.minSize(1));

Practical Examples

Form Validation

// Contact form validation const ContactForm = z.object({ name: z.string().min(1, "Name is required").max(100), email: z.email("Please provide a valid email"), subject: z.string().min(5, "Subject must be at least 5 characters"), message: z.string().min(10, "Message must be at least 10 characters"), newsletter: z.boolean().default(false), });

// React form integration const handleSubmit = (formData: FormData) => { const data = { name: formData.get("name"), email: formData.get("email"), subject: formData.get("subject"), message: formData.get("message"), newsletter: formData.get("newsletter") === "on", };

const result = ContactForm.safeParse(data); if (!result.success) { const errors = result.error.flatten(); return { errors: errors.fieldErrors }; }

// Process valid data return { success: true, data: result.data }; };

API Response Validation

// API response schemas const UserResponse = z.object({ data: z.object({ id: z.number(), name: z.string(), email: z.email(), createdAt: z.string().transform((val) => new Date(val)), }), meta: z.object({ total: z.number(), page: z.number(), totalPages: z.number(), }), });

// Typed API client const apiClient = { async getUser(id: number): Promise<z.infer<typeof UserResponse>["data"]> { const response = await fetch(/api/users/${id}); const data = await response.json();

const result = UserResponse.safeParse(data);
if (!result.success) {
  throw new Error(`Invalid API response: ${result.error.message}`);
}

return result.data.data;

}, };

Configuration Validation

// Application configuration const AppConfig = z.object({ server: z.object({ port: z.coerce.number().min(1).max(65535).default(3000), host: z.string().default("localhost"), cors: z.boolean().default(true), }), database: z.object({ url: z.string(), ssl: z.boolean().default(false), maxConnections: z.coerce.number().int().positive().default(10), }), auth: z.object({ jwtSecret: z.string().min(32), tokenExpiry: z.string().default("24h"), refreshExpiry: z.string().default("7d"), }), });

// Load and validate config const loadConfig = (configPath: string) => { const rawConfig = require(configPath); const config = AppConfig.parse(rawConfig); return config; };

Requirements

  • TypeScript: 4.5+ (recommended for best inference)

  • Runtime: Node.js, browsers, Deno, Bun

  • Bundle size: 5.36kb gzipped (full), 1.88kb gzipped (mini)

Installation

Full Zod v4

npm install zod@^4.0.0

For minimal bundle size

npm install zod@^4.0.0

Then import from "zod/mini"

Key Features

  • Static Type Inference: Automatic TypeScript type generation from schemas

  • Runtime Validation: Comprehensive input validation with detailed errors

  • Performance: 14x faster parsing than v3, optimized for production

  • Tree Shakable: Zod Mini provides 85% bundle size reduction

  • Template Literals: Validate string patterns matching TypeScript template literals

  • File Validation: Built-in File object validation with size and MIME type constraints

  • Recursive Types: Full support for recursive and self-referential schemas

  • JSON Schema: First-party JSON Schema generation

  • Function Validation: Type-safe function definitions with validated inputs/outputs

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

axios

No summary provided by upstream source.

Repository SourceNeeds Review
General

radix-ui

No summary provided by upstream source.

Repository SourceNeeds Review
General

httpx

No summary provided by upstream source.

Repository SourceNeeds Review
General

structlog

No summary provided by upstream source.

Repository SourceNeeds Review