
Zod 4
Install when you need Zod v4 schemas for forms, API bodies, or env parsing—and want v3→v4 syntax without guesswork.
Overview
zod-4 is an agent skill most often used in Build (also Build frontend, Ship testing) that teaches Zod v4 schema patterns and v3→v4 migration for validation and parsing.
Install
npx skills add https://github.com/prowler-cloud/prowler --skill zod-4What is this skill?
- Documents Zod 3→4 breaking changes (top-level z.email(), z.uuid(), z.url(), error option shape)
- Primitives, objects, optional/nullable, unions, and custom error messages in v4 style
- Patterns for forms, request payloads, and adapter layers in TypeScript
- Scoped for root and UI packages in a monorepo-style layout
- Auto-invokes when creating or updating Zod schemas
- v3→v4 breaking-change mapping for email, uuid, url, nonempty, and object errors
Adoption & trust: 722 installs on skills.sh; 14k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You are on Zod 4 but your agent keeps generating Zod 3 APIs that break types and runtime parsing.
Who is it for?
Solo builders standardizing validation during a Zod 4 upgrade or greenfield TypeScript API and UI work.
Skip if: Teams still locked on Zod 3 with no migration plan, or projects using non-Zod validation libraries exclusively.
When should I use this skill?
When creating or updating Zod v4 schemas for validation/parsing (forms, request payloads, adapters), including v3 -> v4 migration patterns.
What do I get? / Deliverables
After the skill runs, schemas use v4 top-level validators and error options consistently across forms and payloads.
- Updated Zod 4 schema modules
- Migration notes from v3 patterns
- Consistent error messages on objects and fields
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Validation schemas are authored while wiring UI forms and server contracts; backend is the canonical shelf because payloads and adapters are the hardest migration surface. Request parsing, shared DTOs, and adapter layers live in backend and integration code even when forms consume the same schemas on the client.
Where it fits
Define shared login and signup schemas before wiring tRPC or route handlers.
Mirror server Zod shapes in React Hook Form or similar client validators.
Tighten edge-case unions and error messages before shipping public API changes.
How it compares
Use for procedural Zod 4 knowledge in the agent—not a generic JSON Schema generator or a one-off MCP validator.
Common Questions / FAQ
Who is zod-4 for?
TypeScript solo builders and small teams who want their coding agent to write correct Zod 4 schemas for UI and server boundaries.
When should I use zod-4?
During Build when adding form schemas, API DTOs, or adapters; during Ship when tightening request validation before release; anytime SKILL.md triggers on creating or updating Zod v4 schemas.
Is zod-4 safe to install?
It allows broad repo edit and shell tools—review the Security Audits panel on this Prism page and your org policies before enabling auto-invoke in sensitive codebases.
SKILL.md
READMESKILL.md - Zod 4
## Breaking Changes from Zod 3 ```typescript // ❌ Zod 3 (OLD) z.string().email() z.string().uuid() z.string().url() z.string().nonempty() z.object({ name: z.string() }).required_error("Required") // ✅ Zod 4 (NEW) z.email() z.uuid() z.url() z.string().min(1) z.object({ name: z.string() }, { error: "Required" }) ``` ## Basic Schemas ```typescript import { z } from "zod"; // Primitives const stringSchema = z.string(); const numberSchema = z.number(); const booleanSchema = z.boolean(); const dateSchema = z.date(); // Top-level validators (Zod 4) const emailSchema = z.email(); const uuidSchema = z.uuid(); const urlSchema = z.url(); // With constraints const nameSchema = z.string().min(1).max(100); const ageSchema = z.number().int().positive().max(150); const priceSchema = z.number().min(0).multipleOf(0.01); ``` ## Object Schemas ```typescript const userSchema = z.object({ id: z.uuid(), email: z.email({ error: "Invalid email address" }), name: z.string().min(1, { error: "Name is required" }), age: z.number().int().positive().optional(), role: z.enum(["admin", "user", "guest"]), metadata: z.record(z.string(), z.unknown()).optional(), }); type User = z.infer<typeof userSchema>; // Parsing const user = userSchema.parse(data); // Throws on error const result = userSchema.safeParse(data); // Returns { success, data/error } if (result.success) { console.log(result.data); } else { console.log(result.error.issues); } ``` ## Arrays and Records ```typescript // Arrays const tagsSchema = z.array(z.string()).min(1).max(10); const numbersSchema = z.array(z.number()).nonempty(); // Records (objects with dynamic keys) const scoresSchema = z.record(z.string(), z.number()); // { [key: string]: number } // Tuples const coordinatesSchema = z.tuple([z.number(), z.number()]); // [number, number] ``` ## Unions and Discriminated Unions ```typescript // Simple union const stringOrNumber = z.union([z.string(), z.number()]); // Discriminated union (more efficient) const resultSchema = z.discriminatedUnion("status", [ z.object({ status: z.literal("success"), data: z.unknown() }), z.object({ status: z.literal("error"), error: z.string() }), ]); ``` ## Transformations ```typescript // Transform during parsing const lowercaseEmail = z.email().transform(email => email.toLowerCase()); // Coercion (convert types) const numberFromString = z.coerce.number(); // "42" → 42 const dateFromString = z.coerce.date(); // "2024-01-01" → Date // Preprocessing const trimmedString = z.preprocess( val => typeof val === "string" ? val.trim() : val, z.string() ); ``` ## Refinements ```typescript const passwordSchema = z.string() .min(8) .refine(val => /[A-Z]/.test(val), { message: "Must contain uppercase letter", }) .refine(val => /[0-9]/.test(val), { message: "Must contain number", }); // With superRefine for multiple errors const formSchema = z.object({ password: z.string(), confirmPassword: z.string(), }).superRefine((data, ctx) => { if (data.password !== data.confirmPassword) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Passwords don't match", path: ["confirmPassword"], }); } }); ``` ## Optional and Nullable ```typescript // Optional (T | undefined) z.string().optional() // Nullable (T | null) z.string().nullable() // Both (T | null | undefined) z.string().nullish() // Default values z.string().default("unknown") z.number().default(() => Math.random()) ``` ## Error Handling ```typescript // Zod 4: Use 'error' param instead of 'mes