Skip to content

/web-forms-zod-validation

Zod schema validation patterns for TypeScript - schema definitions, type inference, refinements, transforms, discriminated unions

shell
$ npx -y skills add agents-inc/skills --skill web-forms-zod-validation --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/web-forms-zod-validation
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

Zod schema validation patterns for TypeScript - schema definitions, type inference, refinements, transforms, discriminated unions

SKILL.md

web-forms-zod-validation.SKILL.md
name: web-forms-zod-validation
description: Zod schema validation patterns for TypeScript - schema definitions, type inference, refinements, transforms, discriminated unions

Zod Schema Validation Patterns

> **Quick Guide:** Use Zod for runtime validation at trust boundaries (API responses, form inputs, config, URL params). Define schemas once, derive types with `z.infer`. Use `safeParse` for error handling, `refine`/`superRefine` for custom validation, `transform` for data conversion. Named constants for all validation limits. > > **Version Note:** Zod v4 is now the stable release (v4.1+). It brings 14.7x faster string parsing, 57% smaller bundle, and new top-level APIs (`z.email()`, `z.url()`, `z.iso.*`). The v3 method-chain equivalents (`z.string().email()`) still work but are deprecated. For migration details, see [reference.md](reference.md).

---

<critical_requirements>

CRITICAL: Before Using This Skill

> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)

**(You MUST use `safeParse` instead of `parse` for user-facing validation - prevents unhandled exceptions)**

**(You MUST use `z.infer<typeof schema>` to derive types - never duplicate schema as separate interface)**

**(You MUST validate at trust boundaries - API responses, form inputs, config files, URL params)**

**(You MUST use named constants for validation limits - NO magic numbers in `.min()`, `.max()`, `.length()`)**

</critical_requirements>

---

**Auto-detection:** Zod schemas, z.object, z.string, z.number, z.infer, safeParse, refine, superRefine, transform, discriminatedUnion, z.coerce, z.pipe, z.catch, z.brand, z.lazy, z.email, z.url, z.iso

**When to use:**

  • Validating API responses before using data
  • Parsing form input data with type safety
  • Validating configuration files or environment variables
  • Defining contracts between systems (frontend/backend shared schemas)
  • Runtime type checking for data from untrusted sources

**When NOT to use:**

  • Internal function parameters (TypeScript is sufficient for trusted data)
  • Simple boolean checks that don't need schema definition
  • Performance-critical hot paths where validation overhead matters

---

<philosophy>

Philosophy

TypeScript provides compile-time type safety for code you control. Zod provides **runtime validation** for data you don't control - API responses, user input, configuration files, URL parameters. Use TypeScript for internal contracts; use Zod at **trust boundaries** where external data enters your system.

**Key principle:** Define the schema once, derive the type. Never maintain parallel type definitions and validation logic - they will drift apart.

// Schema is the source of truth
const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

// Type is derived, always in sync
type User = z.infer<typeof UserSchema>;

</philosophy>

---

<patterns>

Core Patterns

Pattern 1: Schema Definition with Named Constants

Define schemas with named constants for all validation limits. Custom error messages for user-facing fields.

const MIN_USERNAME_LENGTH = 3;
const MAX_USERNAME_LENGTH = 50;

const UserSchema = z.object({
  username: z
    .string()
    .min(
      MIN_USERNAME_LENGTH,
      `Username must be at least ${MIN_USERNAME_LENGTH} characters`,
    )
    .max(
      MAX_USERNAME_LENGTH,
      `Username cannot exceed ${MAX_USERNAME_LENGTH} characters`,
    ),
  email: z.string().email("Invalid email format"),
});

type User = z.infer<typeof UserSchema>; // Always derived, never manual interface

**Why good:** named constants make limits discoverable, custom error messages improve UX, type derived from schema

See [examples/core.md](examples/core.md) for complete schema examples with reusable sub-schemas and CRUD composition patterns.

---

Pattern 2: Safe Parsing for Error Handling

Use `safeParse` for user input and API responses. Reserve `parse` for config/internal data where invalid = programming error.

const result = UserSchema.safeParse(data);

if (!result.success) {
  const errors = result.error.issues.reduce(
    (acc, err) => {
      const field = err.path.join(".");
      acc[field] = err.message;
      return acc;
    },
    {} as Record<string, string>,
  );
  return { success: false, errors };
}

return { success: true, user: result.data };

**Why good:** safeParse never throws, validation errors handled explicitly, error formatting provides useful field-level feedback

See [examples/core.md](examples/core.md) for form validation and API response validation patterns.

---

Pattern 3: Refinements and Cross-Field Validation

Use `refine` for custom validation logic. Use `superRefine` when you need cross-field validation with specific error paths.

const MIN_PASSWORD_LENGTH = 8;

const PasswordFormSchema = z
  .object({
    password: z.string().min(MIN_PASSWORD_LENGTH),
    confirmPassword: z.string(),
  })
  .superRefine((data, ctx) => {
    if (data.password !== data.confirmPassword) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: "Passwords do not match",
        path: ["confirmPassword"],
      });
    }
  });

**Why good:** superRefine enables cross-field validation with specific error paths, keeps all validation in the schema

See [examples/core.md](examples/core.md) for password refinement chains and conditional validation patterns.

---

Pattern 4: Transforms and Type Conversion

Use `transform` to convert data during validation. Use `z.input` and `z.output` when transforms change the type.

const DateSchema = z
  .string()
  .datetime()
  .transform((str) => new Date(str));

type DateInput = z.input<typeof DateSchema>; // string
type DateOutput = z.output<typeof DateSchema>; // Date

**Gotcha:** `z.infer` returns the output type. When a function accepts pre-validati

Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withagents-inc-skills

The official skills marketplace for Agents Inc. 150+ skills covering everything from React and Prisma to Redis, ElevenLabs, and infrastructure tooling. Pick the skills that match your stack and install them via Claude Code. Need more control?

Get the whole plugin, auto-invoked