zod-4
prowler-cloud/prowler/skills/zod-4/SKILL.md
Zod 4 schema validation patterns. Trigger: When creating or updating Zod v4 schemas for validation/parsing (forms, request payloads, adapters), including v3 -> v4 migration patterns.
Skill15k starsChanged 5 months ago
What's in it
- Breaking Changes from Zod 3
- Basic Schemas
- Object Schemas
- Arrays and Records
- Unions and Discriminated Unions
- Transformations
- Refinements
- Optional and Nullable
- Error Handling
- React Hook Form Integration
Tools it asks for
- Read
- Edit
- Write
- Glob
- Grep
- Bash
- WebFetch
- WebSearch
- Task
---
name: zod-4
description: >
Zod 4 schema validation patterns.
Trigger: When creating or updating Zod v4 schemas for validation/parsing (forms, request payloads, adapters), including v3 -> v4 migration patterns.
license: Apache-2.0
metadata:
author: prowler-cloud
version: "1.0"
scope: [root, ui]
auto_invoke: "Creating Zod schemas"
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
---
## 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 'message'
const schema = z.object({
name: z.string({ error: "Name must be a string" }),
email: z.email({ error: "Invalid email format" }),
age: z.number().min(18, { error: "Must be 18 or older" }),
});
// Custom error map
const customSchema = z.string({
error: (issue) => {
if (issue.code === "too_small") {
return "String is too short";
}
return "Invalid string";
},
});
```
## React Hook Form Integration
```typescript
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({
email: z.email(),
password: z.string().min(8),
});
type FormData = z.infer<typeof schema>;
function Form() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} />
{errors.email && <span>{errors.email.message}</span>}
</form>
);
}
```
More agent context in prowler-cloud/prowler
43 other files this repository gives its agents.
Skill
- ai-sdk-5skills/ai-sdk-5/SKILL.md
- django-drfskills/django-drf/SKILL.md
- django-migration-psqlskills/django-migration-psql/SKILL.md
- gh-awskills/gh-aw/SKILL.md
- jsonapiskills/jsonapi/SKILL.md
- nextjs-16skills/nextjs-16/SKILL.md
- playwrightskills/playwright/SKILL.md
- postgresql-indexingskills/postgresql-indexing/SKILL.md
- prowler-apiskills/prowler-api/SKILL.md
- prowler-attack-paths-queryskills/prowler-attack-paths-query/SKILL.md
- prowler-changelogskills/prowler-changelog/SKILL.md
- prowler-ciskills/prowler-ci/SKILL.md
- prowler-commitskills/prowler-commit/SKILL.md
- prowler-compliance-reviewskills/prowler-compliance-review/SKILL.md
- prowler-complianceskills/prowler-compliance/SKILL.md
- prowler-docsskills/prowler-docs/SKILL.md
- prowler-mcpskills/prowler-mcp/SKILL.md
- prowler-providerskills/prowler-provider/SKILL.md
- prowler-prskills/prowler-pr/SKILL.md
- prowler-readme-tableskills/prowler-readme-table/SKILL.md
- prowler-sdk-checkskills/prowler-sdk-check/SKILL.md
- prowlerskills/prowler/SKILL.md
- prowler-test-apiskills/prowler-test-api/SKILL.md
- prowler-test-mcpskills/prowler-test-mcp/SKILL.md
- prowler-test-sdkskills/prowler-test-sdk/SKILL.md
- prowler-test-uiskills/prowler-test-ui/SKILL.md
- prowler-tourskills/prowler-tour/SKILL.md
- prowler-uiskills/prowler-ui/SKILL.md
- pytestskills/pytest/SKILL.md
- react-19skills/react-19/SKILL.md
- skill-creatorskills/skill-creator/SKILL.md
- skill-syncskills/skill-sync/SKILL.md
- tailwind-4skills/tailwind-4/SKILL.md
- tddskills/tdd/SKILL.md
- typescriptskills/typescript/SKILL.md
- vitestskills/vitest/SKILL.md
- zustand-5skills/zustand-5/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

