
Zod Validation Utilities
- 3 installs
- 318 repo stars
- Updated June 22, 2026
- giuseppe-trisciuoglio/developer-kit-claude-code
This is a copy of zod-validation-utilities by giuseppe-trisciuoglio - installs and ranking accrue to the original listing.
Helps with ai & agent building tasks.
About
zod-validation-utilities is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- zod-validation-utilities
- AI & Agent Building
- AI-coding skill
Zod Validation Utilities by the numbers
- 3 all-time installs (skills.sh)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit-claude-code --skill zod-validation-utilitiesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 318 |
| Last updated | June 22, 2026 |
| Repository | giuseppe-trisciuoglio/developer-kit-claude-code ↗ |
What it does
Helps with ai & agent building tasks.
Files
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
1. Start with strict object schemas and explicit field constraints 2. Prefer modern Zod v4 APIs and the error option for error messages 3. Use coercion at boundaries (z.coerce.*) when input types are uncertain 4. Keep business invariants in refine/superRefine close to schema definitions 5. Export both schema and inferred types (z.input/z.output) for consistency 6. Reuse utility schemas (email, id, dates, pagination) to reduce duplication
Validation Workflow
When integrating validation into an API handler or service:
1. Define the schema at the boundary (handler, queue, config loader) 2. Parse with safeParse to handle errors gracefully 3. Check result.success to branch on failure/success 4. Use result.data with full type inference in success path 5. 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
import { 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)
import { 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
import { 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
safeParsefor recoverable flows;parsefor fail-fast execution - Share small schema utilities (
id,email,slug) to enforce consistency - Use
z.inputandz.outputwhen transforms/coercions change runtime shape - Avoid overusing
preprocess; prefer explicitz.coerce.*where possible - Treat external payloads as untrusted and always validate before use
Constraints and Warnings
- Ensure examples match your installed
zodmajor version (v4 APIs shown) erroris the preferred option for custom errors in Zod v4 patterns- Discriminated unions require a stable discriminator key across variants
- Coercion can hide bad upstream data; add bounds and refinements defensively
Advanced Zod v4 Patterns
Advanced validation patterns using Zod v4 for complex use cases.
Discriminated Union with Metadata
import { z } from "zod";
const EventSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("user_created"),
userId: z.string().uuid(),
email: z.email(),
createdAt: z.coerce.date(),
}),
z.object({
type: z.literal("user_deleted"),
userId: z.string().uuid(),
reason: z.string().optional(),
}),
z.object({
type: z.literal("subscription_upgraded"),
userId: z.string().uuid(),
previousPlan: z.enum(["free", "pro", "enterprise"]),
newPlan: z.enum(["pro", "enterprise"]),
effectiveAt: z.coerce.date(),
}),
]);
type Event = z.infer<typeof EventSchema>;
function handleEvent(event: Event) {
switch (event.type) {
case "user_created":
return sendWelcomeEmail(event.email);
case "user_deleted":
return cleanupUserData(event.userId);
case "subscription_upgraded":
return applyPlanFeatures(event.userId, event.newPlan);
}
}Recursive Schema (Tree Structure)
import { z } from "zod";
export const TreeNodeSchema: z.ZodType<{ id: string; children: TreeNode[] }> = z
.object({
id: z.string(),
children: z.lazy(() => z.array(TreeNodeSchema)).default([]),
})
.strict();
type TreeNode = z.infer<typeof TreeNodeSchema>;Map and Set Validation
import { z } from "zod";
export const StringRecordSchema = z.record(z.string());
export const UserMapSchema = z.map(z.string(), z.object({
id: z.string(),
name: z.string(),
}));
export const UniqueTagsSchema = z.set(z.string().min(1).max(20)).max(10);Conditional Schema (or and union)
import { z } from "zod";
export const PricingSchema = z.union([
z.object({ type: z.literal("fixed"), amount: z.number().positive() }),
z.object({ type: z.literal("tiered"), tiers: z.array(z.object({
minQty: z.number().int().nonnegative(),
pricePerUnit: z.number().positive(),
})) }),
]);
export const AdvancedSearchSchema = z.object({
query: z.string().optional(),
filters: z.object({
status: z.enum(["active", "archived", "all"]).default("all"),
dateRange: z.object({
from: z.coerce.date(),
to: z.coerce.date(),
}).optional(),
}).optional(),
});Async Validation (for database checks)
import { z } from "zod";
export const UniqueUsernameSchema = z.string()
.min(3)
.max(20)
.regex(/^[a-zA-Z0-9_]+$/)
.refine(async (username) => {
const exists = await db.user.findUnique({ where: { username } });
return !exists;
}, { error: "Username already taken" });Pipeline Schema (Zod v4 functional pipe)
import { z } from "zod";
export const UsernamePipelineSchema = z
.string()
.trim()
.toLowerCase()
.min(3)
.max(20)
.regex(/^[a-z0-9_]+$/);
export const SlugSchema = z
.string()
.transform((v) => v.toLowerCase().replace(/\s+/g, "-"))
.pipe(z.string().regex(/^[a-z0-9-]+$/));