
zod-schema-creation
Guide through creating Zod validation schemas with i18n integration for API request validation. Use
Zod Validation Schema Creation Skill
Guides through creating Zod validation schemas with i18n integration for API request validation.
Purpose
USE this skill when:
- Creating new API endpoints that accept request bodies
- Adding validation for POST, PUT, or PATCH requests
- Implementing form validation schemas
Critical Rules
⚠️ Factory pattern REQUIRED - Schemas must accept translation function t: (key: string) => string
⚠️ i18n keys in errors - All error messages use translation keys, not hardcoded text
⚠️ File organization - All schemas in shared/utils/schemas/ directory
⚠️ Type export - Always export TypeScript type using z.infer<ReturnType<...>>
Workflow Steps
Step 1: Identify Validation Requirements
- What fields are required/optional?
- What validation rules apply?
- Creating (create schema) or updating (update schema)?
Step 2: Create Schema File
Location: shared/utils/schemas/{resource}.ts
Step 3: Implement Factory Function Pattern
import { z } from "zod"
export function createSpeakerSchema(t: (key: string) => string) {
return z.object({
firstName: z
.string()
.min(1, t("validation.firstNameRequired"))
.max(100, t("validation.firstNameTooLong")),
lastName: z
.string()
.min(1, t("validation.lastNameRequired"))
.max(100),
email: z
.string()
.email(t("validation.emailInvalid"))
.optional(),
})
}
Step 4: Export TypeScript Type
export type CreateSpeakerInput = z.infer<ReturnType<typeof createSpeakerSchema>>
Step 5: Create Update Schema (If Needed)
export function updateSpeakerSchema(t: (key: string) => string) {
return createSpeakerSchema(t).partial()
}
export type UpdateSpeakerInput = z.infer<ReturnType<typeof updateSpeakerSchema>>
Step 6: Update Barrel File
Add to shared/utils/schemas/index.ts:
export * from "./speaker"
Step 7: Add Translation Keys
Use i18n-key-validation skill to ensure keys exist in both pl.json and en.json
Step 8: Implement in API Route
import { createSpeakerSchema } from "#shared/utils/schemas"
export default defineEventHandler(async (event) => {
const body = await validateBody(event, createSpeakerSchema)
// body is now typed and validated
const db = useDrizzle()
const [speaker] = await db.insert(tables.speakers)
.values({
id: crypto.randomUUID(),
...body,
createdAt: new Date(),
updatedAt: new Date(),
})
.returning()
return speaker
})
Common Validation Patterns
// String with length
field: z.string().min(1, t("validation.required")).max(200)
// Email
email: z.string().email(t("validation.emailInvalid")).optional()
// Number
age: z.number().min(0).max(150)
// Enum
status: z.enum(["active", "archived"])
// Array
tags: z.array(z.string()).max(10)
Anti-Patterns
❌ Hardcoded error messages ❌ No factory pattern ❌ Wrong file location ❌ Missing type export
References
- Backend guidelines:
server/AGENTS.md - i18n patterns:
.agents/i18n-patterns.md