zod-patterns
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.
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.staRelated 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.