Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/giuseppe-trisciuoglio-zod-validation-utilities/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 35% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 45% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 39% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 135% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 42% | 0% |
Production-ready Zod v4 patterns for reusable, type-safe validation with minimal boilerplate. Focuses on modern APIs, predictable error handling, and form integration.
zodResolvererror option for error messagesz.coerce.*) when input types are uncertainrefine/superRefine close to schema definitionsz.input/z.output) for consistencyWhen integrating validation into an API handler or service:
safeParse to handle errors gracefullyresult.success to branch on failure/successresult.data with full type inference in success pathSee example 7 (safeParse workflow) for the complete pattern.
tsimport { 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" } );
tsimport { 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>);
tsimport { 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) }), ]);
refine and superRefinetsimport { 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", }); } });
tsimport { 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 });
zodResolver)tsximport { 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", });
safeParsetsimport { 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.
safeParse for recoverable flows; parse for fail-fast executionid, email, slug) to enforce consistencyz.input and z.output when transforms/coercions change runtime shapepreprocess; prefer explicit z.coerce.* where possiblezod major version (v4 APIs shown)error is the preferred option for custom errors in Zod v4 patterns| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 14,770 | 10,788 | -27% | 1 | 1 | 0% | 2,920 | 3,930 | +35% | 0 | 0 | — |
case-02 | fail→pass | 15,444 | 11,658 | -25% | 1 | 1 | 0% | 3,058 | 4,433 | +45% | 0 | 0 | — |
case-03 | fail→pass | 11,002 | 6,427 | -42% | 1 | 1 | 0% | 2,250 | 3,130 | +39% | 0 | 0 | — |
case-04 | pass→pass | 5,908 | 5,525 | -6% | 1 | 1 | 0% | 1,028 | 2,835 | +176% | 0 | 0 | — |
case-05 | pass→pass | 6,412 | 5,699 | -11% | 1 | 1 | 0% | 1,150 | 2,754 | +139% | 0 | 0 | — |
case-06 | fail→pass | 11,450 | 9,086 | -21% | 1 | 1 | 0% | 1,569 | 3,690 | +135% | 0 | 0 | — |
case-07 | fail→fail | 16,186 | 12,110 | -25% | 1 | 1 | 0% | 2,944 | 4,136 | +40% | 0 | 0 | — |
case-08 | fail→pass | 11,601 | 5,335 | -54% | 1 | 1 | 0% | 1,978 | 2,801 | +42% | 0 | 0 | — |
case-09 | pass→pass | 6,817 | 4,961 | -27% | 1 | 1 | 0% | 1,314 | 2,781 | +112% | 0 | 0 | — |
case-10 | pass→pass | 10,647 | 5,750 | -46% | 1 | 1 | 0% | 2,173 | 2,993 | +38% | 0 | 0 | — |
case-11 | fail→pass | 9,438 | 5,068 | -46% | 1 | 1 | 0% | 1,883 | 2,762 | +47% | 0 | 0 | — |
case-12 | pass→pass | 4,948 | 4,251 | -14% | 1 | 1 | 0% | 949 | 2,677 | +182% | 0 | 0 | — |
case-13 | pass→pass | 6,213 | 5,287 | -15% | 1 | 1 | 0% | 1,253 | 2,724 | +117% | 0 | 0 | — |
case-14 | pass→pass | 10,149 | 4,218 | -58% | 1 | 1 | 0% | 1,892 | 2,657 | +40% | 0 | 0 | — |
case-15 | fail→pass | 5,291 | 2,707 | -49% | 1 | 1 | 0% | 893 | 2,253 | +152% | 0 | 0 | — |
case-16 | fail→pass | 5,848 | 2,208 | -62% | 1 | 1 | 0% | 956 | 2,154 | +125% | 0 | 0 | — |
case-17 | fail→pass | 11,286 | 3,239 | -71% | 1 | 1 | 0% | 1,761 | 2,437 | +38% | 0 | 0 | — |
case-18 | pass→pass | 4,422 | 3,512 | -21% | 1 | 1 | 0% | 715 | 2,429 | +240% | 0 | 0 | — |
case-19 | fail→fail | 9,735 | 8,070 | -17% | 1 | 1 | 0% | 1,906 | 3,427 | +80% | 0 | 0 | — |
case-20 | fail→pass | 8,419 | 8,471 | +1% | 1 | 1 | 0% | 1,549 | 3,193 | +106% | 0 | 0 | — |
case-21 | pass→pass | 11,806 | 7,592 | -36% | 1 | 1 | 0% | 2,038 | 3,059 | +50% | 0 | 0 | — |
case-22 | pass→pass | 4,837 | 4,564 | -6% | 1 | 1 | 0% | 898 | 2,722 | +203% | 0 | 0 | — |
case-23 | pass→pass | 14,935 | 7,842 | -47% | 1 | 1 | 0% | 2,851 | 3,340 | +17% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +43 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.