Skip to content
Security
Skill

/zod-4

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.

From plugin
prowler
15k39 skills1 MCP

Claude Code

$ npx -y skills add prowler-cloud/prowler --skill zod-4 --agent claude-code

SKILL.md

zod-4.SKILL.md
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.1"
  scope: [root, ui]
  auto_invoke: "Creating Zod schemas"
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task

Breaking Changes from Zod 3

// ❌ Zod 3 (OLD)
z.string().email()
z.string().uuid()
z.string().url()
z.string({ required_error: "Required", invalid_type_error: "Not a string" })
z.string().min(1, { message: "Required" })
schema.merge(other)
z.object({...}).strict()
z.ZodIssueCode.custom

// ✅ Zod 4 (NEW)
z.email()
z.uuid()
z.url()
z.string({ error: "Required" })          // single `error` param (string or function)
z.string().min(1, { error: "Required" }) // `message` still works but is deprecated
schema.extend(other.shape)
z.strictObject({...})
"custom"                                  // issue code literal in ctx.addIssue

`z.ZodIssueCode.custom` still compiles in Zod 4 (deprecated) and is used in `ui/types/formSchemas.ts` and `ui/types/integrations.ts`; use `code: "custom"` in new code.

Prowler UI schemas live in `ui/types/formSchemas.ts`, `ui/types/authFormSchema.ts`, `ui/types/integrations.ts` and next to the feature that uses them.

Basic Schemas

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

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

// 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

// 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

// 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

const passwordSchema = z.string()
  .min(8)
  .refine(val => /[A-Z]/.test(val), {
    error: "Must contain uppercase letter",
  })
  .refine(val => /[0-9]/.test(val), {
    error: "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: "custom",
      message: "Passwords don't match",
      path: ["confirmPassword"],
    });
  }
});

Optional and Nullable

// 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

// 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

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>
  );
}
Read more
@prowler-cloud
Ships withprowler

Prowler is the world’s most widely used Open-Source Cloud Security Platform that automates security and compliance across any cloud environment.

15k39 skills1 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
15.2k
Star history of prowler-cloud/prowler on GitHub, a chart by star-history.com

Other skills on prowler.

ai-sdk-5

ai-sdk-5

Vercel AI SDK patterns for the installed major (ui/package.json: `ai` 6.x, `@ai-sdk/react` 3.x). The skill name is historical;…

django-drf

django-drf

Django REST Framework patterns. Trigger: When implementing generic DRF APIs (ViewSets, serializers, routers, permissions,…

gh-aw

gh-aw

Create and maintain GitHub Agentic Workflows (gh-aw) for Prowler. Trigger: When creating agentic workflows, modifying gh-aw…

jsonapi

jsonapi

Strict JSON:API v1.1 specification compliance. Trigger: When creating or modifying API endpoints, reviewing API responses, or…

More skills from other plugins.

api-security

api-security

Use for authorized security assessment of REST, GraphQL, WebSocket, or SOAP APIs, including discovery, authentication,…

@zhaoxuya520@zhaoxuya520Install Skill
nemoclaw-user-guide

nemoclaw-user-guide

Find official NemoClaw documentation for installation, configuration, operation, or troubleshooting. Also handles requested docs…

nemoclaw10d23k
@nvidia@nvidiaInstall Skill

Questions about zod-4.

Which coding agents can use zod-4?
zod-4 installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.