Validates data with Zod schemas including type inference, transformations, error handling, and form integration. Use when validating API inputs, form data, environment variables, or any runtime data validation.
Validates runtime data using Zod schemas with type inference and error handling. Use when validating API inputs, environment variables, form data, or any data that needs runtime type checking.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
TypeScript-first schema validation with static type inference.
Install:
npm install zod
Basic usage:
import { z } from 'zod';
// Define schema
const UserSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(18),
});
// Infer TypeScript type
type User = z.infer<typeof UserSchema>;
// Validate data
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data); // Typed as User
} else {
console.log(result.error.issues);
}
// Strings
const name = z.string();
const email = z.string().email();
const url = z.string().url();
const uuid = z.string().uuid();
const cuid = z.string().cuid();
const regex = z.string().regex(/^[a-z]+$/);
const minMax = z.string().min(1).max(100);
const length = z.string().length(10);
const nonempty = z.string().min(1, 'Required');
// Numbers
const age = z.number();
const positive = z.number().positive();
const negative = z.number().negative();
const integer = z.number().int();
const range = z.number().min(0).max(100);
const finite = z.number().finite();
// BigInt
const bigint = z.bigint();
// Boolean
const active = z.boolean();
// Date
const date = z.date();
const minDate = z.date().min(new Date('2024-01-01'));
const maxDate = z.date().max(new Date());
// Undefined/Null
const undef = z.undefined();
const nul = z.null();
const nullable = z.string().nullable(); // string | null
const optional = z.string().optional(); // string | undefined
const nullish = z.string().nullish(); // string | null | undefined
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
email: z.string().email(),
age: z.number().optional(),
});
type User = z.infer<typeof UserSchema>;
// { id: string; name: string; email: string; age?: number }
const schema = z.object({
name: z.string(),
email: z.string(),
age: z.number(),
});
// All properties optional
const PartialSchema = schema.partial();
// { name?: string; email?: string; age?: number }
// All properties required
const RequiredSchema = schema.required();
// Pick specific properties
const NameEmail = schema.pick({ name: true, email: true });
// { name: string; email: string }
// Omit specific properties
const NoAge = schema.omit({ age: true });
// { name: string; email: string }
// Extend with new properties
const ExtendedSchema = schema.extend({
role: z.enum(['admin', 'user']),
});
// Merge schemas
const merged = schema.merge(z.object({ role: z.string() }));
// Make specific properties optional
const PartialAge = schema.partial({ age: true });
// { name: string; email: string; age?: number }
// Strict: fail if unknown keys present
const StrictSchema = z.object({ name: z.string() }).strict();
// Passthrough: allow and preserve unknown keys
const PassthroughSchema = z.object({ name: z.string() }).passthrough();
// Strip: remove unknown keys (default)
const StripSchema = z.object({ name: z.string() }).strip();
// Arrays
const strings = z.array(z.string());
const numbers = z.number().array(); // Alternative syntax
const nonEmpty = z.array(z.string()).nonempty();
const lengthRange = z.array(z.string()).min(1).max(10);
const exactLength = z.array(z.string()).length(5);
// Tuples (fixed length, specific types)
const tuple = z.tuple([z.string(), z.number(), z.boolean()]);
// [string, number, boolean]
// Tuple with rest
const tupleWithRest = z.tuple([z.string()]).rest(z.number());
// [string, ...number[]]
// Union types
const StringOrNumber = z.union([z.string(), z.number()]);
// string | number
// Shorthand
const StringOrNumber2 = z.string().or(z.number());
// Discriminated unions (better performance, error messages)
const Result = z.discriminatedUnion('status', [
z.object({ status: z.literal('success'), data: z.string() }),
z.object({ status: z.literal('error'), error: z.string() }),
]);
// Zod enum
const RoleSchema = z.enum(['admin', 'user', 'guest']);
type Role = z.infer<typeof RoleSchema>; // 'admin' | 'user' | 'guest'
// Get enum values
RoleSchema.options; // ['admin', 'user', 'guest']
// Native enum
enum Status {
Active = 'active',
Inactive = 'inactive',
}
const StatusSchema = z.nativeEnum(Status);
const admin = z.literal('admin');
const fortyTwo = z.literal(42);
const trueLiteral = z.literal(true);
// Transform string to number
const StringToNumber = z.string().transform((val) => parseInt(val, 10));
// Parse and transform
const DateString = z.string().transform((val) => new Date(val));
// With validation after transform
const PositiveNumber = z.string()
.transform((val) => parseInt(val, 10))
.pipe(z.number().positive());
// Automatic coercion
const coercedString = z.coerce.string(); // Any -> string
const coercedNumber = z.coerce.number(); // Any -> number
const coercedBoolean = z.coerce.boolean(); // Any -> boolean
const coercedDate = z.coerce.date(); // Any -> Date
// Common use: form data
const FormSchema = z.object({
age: z.coerce.number().min(0).max(120),
active: z.coerce.boolean(),
date: z.coerce.date(),
});
// Run function before validation
const TrimmedString = z.preprocess(
(val) => (typeof val === 'string' ? val.trim() : val),
z.string()
);
// Handle null/undefined
const NullableToDefault = z.preprocess(
(val) => val ?? 'default',
z.string()
);
const WithDefault = z.string().default('default value');
const WithDefaultFn = z.string().default(() => generateId());
// Catch: use default on parse failure
const SafeNumber = z.number().catch(0);
const SafeString = z.string().catch('');
// Simple refinement
const PasswordSchema = z.string()
.min(8, 'Password must be at least 8 characters')
.refine((val) => /[A-Z]/.test(val), {
message: 'Password must contain uppercase letter',
})
.refine((val) => /[0-9]/.test(val), {
message: 'Password must contain number',
});
// Superrefine (multiple issues)
const PasswordSchema2 = z.string().superRefine((val, ctx) => {
if (val.length < 8) {
ctx.addIssue({
code: z.ZodIssueCode.too_small,
minimum: 8,
type: 'string',
inclusive: true,
message: 'Password must be at least 8 characters',
});
}
if (!/[A-Z]/.test(val)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Password must contain uppercase letter',
});
}
});
const SignupSchema = z.object({
password: z.string().min(8),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'], // Error on specific field
});
const result = UserSchema.safeParse(data);
if (result.success) {
// result.data is typed
console.log(result.data);
} else {
// result.error is ZodError
console.log(result.error.issues);
}
try {
const user = UserSchema.parse(data);
// user is typed
} catch (error) {
if (error instanceof z.ZodError) {
console.log(error.issues);
}
}
const result = UserSchema.safeParse(data);
if (!result.success) {
// Flat format
const flat = result.error.flatten();
// { formErrors: string[], fieldErrors: { [key]: string[] } }
// Formatted
const formatted = result.error.format();
// { _errors: string[], field: { _errors: string[] } }
// Custom format
const issues = result.error.issues.map((issue) => ({
path: issue.path.join('.'),
message: issue.message,
}));
}
const UserSchema = z.object({
name: z.string({
required_error: 'Name is required',
invalid_type_error: 'Name must be a string',
}).min(1, 'Name cannot be empty'),
email: z.string()
.email({ message: 'Invalid email format' }),
age: z.number()
.min(18, { message: 'Must be 18 or older' })
.max(120, { message: 'Invalid age' }),
});
const CreatePostSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().min(1),
tags: z.array(z.string()).default([]),
published: z.boolean().default(false),
});
// In API handler
export async function POST(request: Request) {
const body = await request.json();
const result = CreatePostSchema.safeParse(body);
if (!result.success) {
return Response.json(
{ error: result.error.flatten().fieldErrors },
{ status: 400 }
);
}
const post = await createPost(result.data);
return Response.json(post);
}
const EnvSchema = z.object({
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
});
export const env = EnvSchema.parse(process.env);
const ContactFormSchema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email'),
message: z.string().min(10, 'Message too short').max(1000),
subscribe: z.coerce.boolean().default(false),
});
// Parse FormData
function parseFormData(formData: FormData) {
return ContactFormSchema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
subscribe: formData.get('subscribe'),
});
}
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const FormSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type FormData = z.infer<typeof FormSchema>;
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(FormSchema),
});
const onSubmit = (data: FormData) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<input type="password" {...register('password')} />
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">Login</button>
</form>
);
}
'use server';
import { z } from 'zod';
const CreatePostSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().min(1),
});
export async function createPost(formData: FormData) {
const result = CreatePostSchema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
});
if (!result.success) {
return { error: result.error.flatten().fieldErrors };
}
const post = await db.posts.create({ data: result.data });
return { data: post };
}
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
});
// Infer type from schema
type User = z.infer<typeof UserSchema>;
// Input type (before transforms)
type UserInput = z.input<typeof UserSchema>;
// Output type (after transforms)
type UserOutput = z.output<typeof UserSchema>;
| Mistake | Fix |
|---|---|
| Using parse without try/catch | Use safeParse instead |
| Not handling errors | Check result.success |
| Duplicating types | Use z.infer<typeof Schema> |
| Ignoring transforms | Remember input vs output types |
| Over-complex schemas | Break into smaller schemas |
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.