zod
Zod schema validation patterns and type inference. Auto-loads when validating schemas, parsing data, validating forms, checking types at runtime, or using z.object/z.string/z.infer in TypeScript.
What this skill does
# Zod Schema Validation
TypeScript-first schema declaration and validation library with static type inference.
## Why Zod
- Zero dependencies, 2kb gzipped
- Works in Node.js and browsers
- Immutable API - methods return new instances
- Static type inference - no redundant type declarations
- JSON Schema conversion built-in
## Requirements
- TypeScript v5.5+
- Enable `strict` mode in tsconfig.json
## Core Concepts
### Schema Definition
Always define schemas before validation:
```typescript
import { z } from "zod";
// Object schema
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().int().positive(),
role: z.enum(["admin", "user", "guest"]),
});
// Infer TypeScript type from schema
type User = z.infer<typeof UserSchema>;
```
### Parsing Methods
**`.parse()`** - Throws `ZodError` on failure:
```typescript
try {
const user = UserSchema.parse(data);
// user is typed as User
} catch (e) {
if (e instanceof z.ZodError) {
console.error(e.issues);
}
}
```
**`.safeParse()`** - Returns discriminated union (preferred):
```typescript
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data); // typed User
} else {
console.error(result.error.issues);
}
```
**Async variants** - Required for async refinements/transforms:
```typescript
await UserSchema.parseAsync(data);
await UserSchema.safeParseAsync(data);
```
## Primitive Types
```typescript
// Basic primitives
z.string()
z.number()
z.bigint()
z.boolean()
z.date()
z.symbol()
z.undefined()
z.null()
z.void()
z.any()
z.unknown()
z.never()
// Coercion - converts input to target type
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)
```
## String Validations
```typescript
z.string()
.min(1) // Minimum length
.max(255) // Maximum length
.length(10) // Exact length
.email() // Email format
.url() // URL format
.uuid() // UUID format
.cuid() // CUID format
.regex(/pattern/) // Custom regex
.startsWith("prefix")
.endsWith("suffix")
.includes("substring")
.trim() // Transform: trim whitespace
.toLowerCase() // Transform: lowercase
.toUpperCase() // Transform: uppercase
```
## Number Validations
```typescript
z.number()
.int() // Integer only
.positive() // > 0
.nonnegative() // >= 0
.negative() // < 0
.nonpositive() // <= 0
.gt(5) // > 5
.gte(5) // >= 5 (alias: .min())
.lt(10) // < 10
.lte(10) // <= 10 (alias: .max())
.multipleOf(5) // Divisible by 5
.finite() // Excludes Infinity
.safe() // Safe integer range
```
## Object Schemas
```typescript
const PersonSchema = z.object({
name: z.string(),
age: z.number(),
});
// Make all properties optional
PersonSchema.partial();
// Make specific properties optional
PersonSchema.partial({ age: true });
// Make all properties required
PersonSchema.required();
// Pick specific properties
PersonSchema.pick({ name: true });
// Omit specific properties
PersonSchema.omit({ age: true });
// Extend with new properties
PersonSchema.extend({
email: z.string().email(),
});
// Strict mode - reject unknown keys
PersonSchema.strict();
// Passthrough - preserve unknown keys
PersonSchema.passthrough();
// Strip unknown keys (default behavior)
PersonSchema.strip();
```
## Arrays and Tuples
```typescript
// Array of strings
z.array(z.string())
.min(1) // At least 1 element
.max(10) // At most 10 elements
.length(5) // Exactly 5 elements
.nonempty(); // At least 1 element (typed)
// Alternative syntax
z.string().array();
// Tuple with fixed positions
z.tuple([
z.string(), // First element: string
z.number(), // Second element: number
]);
// Tuple with rest elements
z.tuple([z.string(), z.number()]).rest(z.boolean());
```
## Unions and Enums
```typescript
// Union types
z.union([z.string(), z.number()]);
// Shorthand
z.string().or(z.number());
// Discriminated unions (better error messages)
z.discriminatedUnion("type", [
z.object({ type: z.literal("email"), email: z.string() }),
z.object({ type: z.literal("phone"), phone: z.string() }),
]);
// Enum from array
z.enum(["admin", "user", "guest"]);
// Native enum
enum Role { Admin, User }
z.nativeEnum(Role);
```
## Optional and Nullable
```typescript
// Optional - allows undefined
z.string().optional(); // string | undefined
// Nullable - allows null
z.string().nullable(); // string | null
// Both
z.string().nullish(); // string | null | undefined
// Default values
z.string().default("anonymous");
z.string().optional().default("anonymous");
// Catch - use default on parse failure
z.string().catch("fallback");
```
## Transforms
```typescript
// Transform output type
const StringToNumber = z.string().transform((val) => parseInt(val, 10));
type Output = z.output<typeof StringToNumber>; // number
// Chain transforms
z.string()
.trim()
.toLowerCase()
.transform((val) => val.split(","));
// Preprocess input before validation
z.preprocess(
(val) => String(val),
z.string().min(1)
);
```
## Refinements
```typescript
// Custom validation
z.string().refine(
(val) => val.length <= 255,
{ message: "String must be 255 chars or less" }
);
// Async refinement
z.string().refine(
async (val) => await checkUnique(val),
{ message: "Value must be unique" }
);
// Super refine for complex validations
z.object({
password: z.string(),
confirm: z.string(),
}).superRefine((data, ctx) => {
if (data.password !== data.confirm) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Passwords don't match",
path: ["confirm"],
});
}
});
```
## Error Handling
```typescript
const result = schema.safeParse(data);
if (!result.success) {
// Access all issues
result.error.issues.forEach((issue) => {
console.log(issue.path); // Field path
console.log(issue.message); // Error message
console.log(issue.code); // Error code
});
// Flatten for form errors
const flat = result.error.flatten();
// { formErrors: string[], fieldErrors: { [key]: string[] } }
// Format for display
const formatted = result.error.format();
// { _errors: string[], field: { _errors: string[] } }
}
```
## Common Patterns
### API Request Validation
```typescript
const CreateUserRequest = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1).max(100),
});
// In Express/Fastify handler
const body = CreateUserRequest.parse(req.body);
```
### Environment Variables
```typescript
const EnvSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1),
});
export const env = EnvSchema.parse(process.env);
```
### Form Data
```typescript
const ContactForm = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
message: z.string().min(10, "Message must be at least 10 characters"),
});
// With React Hook Form
const { register, handleSubmit } = useForm({
resolver: zodResolver(ContactForm),
});
```
### API Response
```typescript
const ApiResponse = z.object({
data: z.array(UserSchema),
pagination: z.object({
page: z.number(),
total: z.number(),
}),
});
const response = await fetch("/api/users");
const json = await response.json();
const validated = ApiResponse.parse(json);
```
## JSON Schema Conversion
```typescript
import { z } from "zod";
// Zod to JSON Schema
const jsonSchema = z.toJSONSchema(UserSchema);
// JSON Schema to Zod
const Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.