Developer-kit zod-validation-utilities
Creates reusable Zod v4 schemas, validates API payloads, forms, and configuration input, transforms and coerces data safely, and handles validation errors with strong type inference for TypeScript applications. Use when designing validation layers, parsing `z.string()`, `z.object()`, or `z.email()` schemas, or implementing runtime type-safe data validation.
install
source · Clone the upstream repo
git clone https://github.com/giuseppe-trisciuoglio/developer-kit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/giuseppe-trisciuoglio/developer-kit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/developer-kit-typescript/skills/zod-validation-utilities" ~/.claude/skills/giuseppe-trisciuoglio-developer-kit-zod-validation-utilities && rm -rf "$T"
manifest:
plugins/developer-kit-typescript/skills/zod-validation-utilities/SKILL.mdsource content
Zod Validation Utilities
Overview
Production-ready Zod v4 patterns for reusable, type-safe validation with minimal boilerplate. Focuses on modern APIs, predictable error handling, and form integration.
When to Use
- Defining request/response validation schemas in TypeScript services
- Parsing untrusted input from APIs, forms, env vars, or external systems
- Standardizing coercion, transforms, and cross-field validation
- Building reusable schema utilities across teams
- Integrating React Hook Form with Zod using
zodResolver
Instructions
- Start with strict object schemas and explicit field constraints
- Prefer modern Zod v4 APIs and the
option for error messageserror - Use coercion at boundaries (
) when input types are uncertainz.coerce.* - Keep business invariants in
/refine
close to schema definitionssuperRefine - Export both schema and inferred types (
/z.input
) for consistencyz.output - Reuse utility schemas (email, id, dates, pagination) to reduce duplication
Validation Workflow
When integrating validation into an API handler or service:
- Define the schema at the boundary (handler, queue, config loader)
- Parse with
to handle errors gracefullysafeParse - Check
to branch on failure/successresult.success - Use
with full type inference in success pathresult.data - Return formatted errors or proceed with validated data
See example 7 (
safeParse workflow) for the complete pattern.
Examples
1) Modern Zod 4 primitives and object errors
import { z } from "zod"; export const UserIdSchema = z.uuid({ error: "Invalid user id" }); export const EmailSchema = z.email({ error: "Invalid email" }); export const WebsiteSchema = z.url({ error: "Invalid URL" }); export const UserProfileSchema = z.object( { id: UserIdSchema, email: EmailSchema, website: WebsiteSchema.optional(), }, { error: "Invalid user profile payload" } );
2) Coercion, preprocess, and transform
import { z } from "zod"; export const PaginationQuerySchema = z.object({ page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(20), includeArchived: z.coerce.boolean().default(false), }); export const DateFromUnknownSchema = z.preprocess( (value) => (typeof value === "string" || value instanceof Date ? value : undefined), z.coerce.date({ error: "Invalid date" }) ); export const NormalizedEmailSchema = z .string() .trim() .toLowerCase() .email({ error: "Invalid email" }) .transform((value) => value as Lowercase<string>);
3) Complex schema structures
import { z } from "zod"; const TagSchema = z.string().trim().min(1).max(40); export const ProductSchema = z.object({ sku: z.string().min(3).max(24), tags: z.array(TagSchema).max(15), attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])), dimensions: z.tuple([z.number().positive(), z.number().positive(), z.number().positive()]), }); export const PaymentMethodSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal("card"), last4: z.string().regex(/^\d{4}$/) }), z.object({ type: z.literal("paypal"), email: z.email() }), z.object({ type: z.literal("wire"), iban: z.string().min(10) }), ]);
4) refine
and superRefine
refinesuperRefineimport { z } from "zod"; export const PasswordSchema = z .string() .min(12) .refine((v) => /[A-Z]/.test(v), { error: "Must include an uppercase letter" }) .refine((v) => /\d/.test(v), { error: "Must include a number" }); export const RegisterSchema = z .object({ email: z.email(), password: PasswordSchema, confirmPassword: z.string(), }) .superRefine((data, ctx) => { if (data.password !== data.confirmPassword) { ctx.addIssue({ code: "custom", path: ["confirmPassword"], message: "Passwords do not match", }); } });
5) Optional, nullable, nullish, and default
import { z } from "zod"; export const UserPreferencesSchema = z.object({ nickname: z.string().min(2).optional(), // undefined allowed bio: z.string().max(280).nullable(), // null allowed avatarUrl: z.url().nullish(), // null or undefined allowed locale: z.string().default("en"), // fallback when missing });
6) React Hook Form integration (zodResolver
)
zodResolverimport { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; const ProfileFormSchema = z.object({ name: z.string().min(2, { error: "Name too short" }), email: z.email({ error: "Invalid email" }), age: z.coerce.number().int().min(18), }); type ProfileFormInput = z.input<typeof ProfileFormSchema>; type ProfileFormOutput = z.output<typeof ProfileFormSchema>; const form = useForm<ProfileFormInput, unknown, ProfileFormOutput>({ resolver: zodResolver(ProfileFormSchema), criteriaMode: "all", });
7) Error handling workflow with safeParse
safeParseimport { z } from "zod"; import type { ZodError } from "zod"; const ResultSchema = z.object({ id: z.string(), name: z.string() }); function parseAndHandle(input: unknown) { const result = ResultSchema.safeParse(input); if (!result.success) { const error = result.error as ZodError; console.error("Validation failed:", error.errors); return { success: false as const, error: error.format() }; } return { success: true as const, data: result.data }; }
Tip: For advanced discriminated union patterns and complex React Hook Form workflows, see
.references/advanced-patterns.md
Best Practices
- Keep schemas near boundaries (HTTP handlers, queues, config loaders)
- Prefer
for recoverable flows;safeParse
for fail-fast executionparse - Share small schema utilities (
,id
,email
) to enforce consistencyslug - Use
andz.input
when transforms/coercions change runtime shapez.output - Avoid overusing
; prefer explicitpreprocess
where possiblez.coerce.* - Treat external payloads as untrusted and always validate before use
Constraints and Warnings
- Ensure examples match your installed
major version (v4 APIs shown)zod
is the preferred option for custom errors in Zod v4 patternserror- Discriminated unions require a stable discriminator key across variants
- Coercion can hide bad upstream data; add bounds and refinements defensively