zod-4 — how to use zod-4 how to use zod-4, zod-4 vs zod-3, zod-4 setup guide, zod-4 astro integration, zod-4 vue tutorial, what is zod-4

v1.0.0
GitHub

About this Skill

Perfect for AI Agents requiring robust data validation and schema enforcement in TypeScript environments. zod-4 is a JavaScript library for validating and parsing data, providing a robust set of tools for defining schemas, handling errors, and integrating with popular frameworks like Astro and Vue.

Features

Supports schema definitions using z.string().email() and z.email()
Provides parsing and transformations for complex data types
Offers error handling and form integration for Astro and Vue projects
Includes a migration guide from Zod 3 to Zod 4 for seamless upgrades
Allows for tree-shaking and better performance with recommended methods

# Core Topics

dallay dallay
[0]
[0]
Updated: 3/9/2026

Quality Score

Top 5%
42
Excellent
Based on code quality & docs
Installation
SYS Universal Install (Auto-Detect)
Cursor IDE Windsurf IDE VS Code IDE
> npx killer-skills add dallay/cvix/zod-4

Agent Capability Analysis

The zod-4 MCP Server by dallay is an open-source Community integration for Claude and other AI agents, enabling seamless task automation and capability expansion. Optimized for how to use zod-4, zod-4 vs zod-3, zod-4 setup guide.

Ideal Agent Persona

Perfect for AI Agents requiring robust data validation and schema enforcement in TypeScript environments.

Core Value

Empowers agents to implement comprehensive runtime type checking with Zod 4's optimized parsing and transformation capabilities. Provides superior error handling with custom validation messages and supports modern tree-shaking for reduced bundle size in agent deployments.

Capabilities Granted for zod-4 MCP Server

Validating API response payloads
Enforcing form data integrity in Vue applications
Parsing environment variables with strict schemas
Transforming data with Zod's refinement methods

! Prerequisites & Limits

  • Requires TypeScript 4.5+
  • Limited to JavaScript/TypeScript runtime environments
  • Migration needed from deprecated Zod 3 methods
Project
SKILL.md
11.0 KB
.cursorrules
1.2 KB
package.json
240 B
Ready
UTF-8

# Tags

[No tags]
SKILL.md
Readonly

Zod 4 Best Practices

This document outlines best practices for using Zod 4 in Astro and Vue projects, focusing on schema definitions, parsing, transformations, error handling, and form integration.

Migration Guide from Zod 3 to Zod 4

typescript
1// ⚠️ Deprecated (still works in v4, will be removed in v5) 2z.string().email() 3z.string().uuid() 4z.string().url() 5z.string().nonempty() 6z.string().min(5, { message: "Too short" }) 7 8// ✅ Recommended (better performance, tree-shaking) 9z.email() 10z.uuid() 11z.url() 12z.string().min(1) 13z.string().min(5, { error: "Too short" }) 14 15// Note: Old patterns still work in v4 for backward compatibility

Error Message Parameters

typescript
1// Both work in Zod 4, but 'error' is preferred 2z.string().min(5, { message: "Too short" }) // ⚠️ Deprecated 3z.string().min(5, { error: "Too short" }) // ✅ Preferred 4 5// Error can be a function for dynamic messages 6z.string({ 7 error: (issue) => issue.input === undefined 8 ? "Field is required" 9 : "Invalid string" 10})

Basic Schemas

typescript
1import { z } from "zod"; 2 3// Primitives 4const stringSchema = z.string(); 5const numberSchema = z.number(); 6const booleanSchema = z.boolean(); 7const dateSchema = z.date(); 8 9// Top-level validators (Zod 4) 10const emailSchema = z.email(); 11const uuidSchema = z.uuid(); 12const urlSchema = z.url(); 13 14// With constraints 15const nameSchema = z.string().min(1).max(100); 16const ageSchema = z.number().int().positive().max(150); 17const priceSchema = z.number().min(0).multipleOf(0.01);

Object Schemas

typescript
1const userSchema = z.object({ 2 id: z.uuid(), 3 email: z.email({ error: "Invalid email address" }), 4 name: z.string().min(1, { error: "Name is required" }), 5 age: z.number().int().positive().optional(), 6 role: z.enum(["admin", "user", "guest"]), 7 metadata: z.record(z.string(), z.unknown()).optional(), 8}); 9 10type User = z.infer<typeof userSchema>; 11 12// Parsing 13const user = userSchema.parse(data); // Throws on error 14const result = userSchema.safeParse(data); // Returns { success, data/error } 15 16if (result.success) { 17 console.log(result.data); 18} else { 19 console.log(result.error.issues); 20}

Arrays and Records

typescript
1// Arrays 2const tagsSchema = z.array(z.string()).min(1).max(10); 3const numbersSchema = z.array(z.number()).min(1); 4 5// Records (objects with dynamic keys) 6const scoresSchema = z.record(z.string(), z.number()); 7// { [key: string]: number } 8 9// Tuples 10const coordinatesSchema = z.tuple([z.number(), z.number()]); 11// [number, number]

Unions and Discriminated Unions

typescript
1// Simple union 2const stringOrNumber = z.union([z.string(), z.number()]); 3 4// Discriminated union (more efficient) 5const resultSchema = z.discriminatedUnion("status", [ 6 z.object({ status: z.literal("success"), data: z.unknown() }), 7 z.object({ status: z.literal("error"), error: z.string() }), 8]);

Transformations

typescript
1// Transform during parsing 2const lowercaseEmail = z.email().transform(email => email.toLowerCase()); 3 4// Coercion (convert types) 5const numberFromString = z.coerce.number(); // "42" → 42 6const dateFromString = z.coerce.date(); // "2024-01-01" → Date 7 8// Preprocessing 9const trimmedString = z.preprocess( 10 val => typeof val === "string" ? val.trim() : val, 11 z.string() 12);

Refinements

typescript
1const passwordSchema = z.string() 2 .min(8) 3 .refine(val => /[A-Z]/.test(val), { 4 message: "Must contain uppercase letter", 5 }) 6 .refine(val => /[0-9]/.test(val), { 7 message: "Must contain number", 8 }); 9 10// With superRefine for multiple errors 11const formSchema = z.object({ 12 password: z.string(), 13 confirmPassword: z.string(), 14}).superRefine((data, ctx) => { 15 if (data.password !== data.confirmPassword) { 16 ctx.addIssue({ 17 code: z.ZodIssueCode.custom, 18 message: "Passwords don't match", 19 path: ["confirmPassword"], 20 }); 21 } 22});

Optional and Nullable

typescript
1// Optional (T | undefined) 2z.string().optional() 3 4// Nullable (T | null) 5z.string().nullable() 6 7// Both (T | null | undefined) 8z.string().nullish() 9 10// Default values 11z.string().default("unknown") 12z.number().default(() => Math.random())

Error Handling

typescript
1// Zod 4: Use 'error' param instead of 'message' 2const schema = z.object({ 3 name: z.string({ error: "Name must be a string" }), 4 email: z.email({ error: "Invalid email format" }), 5 age: z.number().min(18, { error: "Must be 18 or older" }), 6}); 7 8// Custom error map 9const customSchema = z.string({ 10 error: (issue) => { 11 if (issue.code === "too_small") { 12 return "String is too short"; 13 } 14 return "Invalid string"; 15 }, 16});

Form Validation in Vue

vue
1<script setup lang="ts"> 2import { ref, computed } from 'vue'; 3import { z } from 'zod'; 4 5const loginSchema = z.object({ 6 email: z.email({ error: 'Invalid email address' }), 7 password: z.string().min(8, { error: 'Password must be at least 8 characters' }), 8}); 9 10type LoginForm = z.infer<typeof loginSchema>; 11 12const formData = ref<Partial<LoginForm>>({ 13 email: '', 14 password: '', 15}); 16 17const errors = ref<Partial<Record<keyof LoginForm, string>>>({}); 18 19const validateField = (field: keyof LoginForm) => { 20 const schema = loginSchema.pick({ [field]: true }); 21 const result = schema.safeParse({ [field]: formData.value[field] }); 22 23 if (!result.success) { 24 errors.value[field] = result.error.issues[0]?.message; 25 } else { 26 delete errors.value[field]; 27 } 28}; 29 30const handleSubmit = async () => { 31 const result = loginSchema.safeParse(formData.value); 32 33 if (!result.success) { 34 errors.value = {}; 35 result.error.issues.forEach(issue => { 36 const field = issue.path[0] as keyof LoginForm; 37 errors.value[field] = issue.message; 38 }); 39 return; 40 } 41 42 console.log('Form submitted:', result.data); 43 // Send to API 44}; 45</script> 46 47<template> 48 <form @submit.prevent="handleSubmit" class="space-y-4"> 49 <div> 50 <input 51 v-model="formData.email" 52 type="email" 53 placeholder="Email" 54 @blur="validateField('email')" 55 :class="{ 'border-red-500': errors.email }" 56 /> 57 <span v-if="errors.email" class="text-red-500 text-sm">{{ errors.email }}</span> 58 </div> 59 60 <div> 61 <input 62 v-model="formData.password" 63 type="password" 64 placeholder="Password" 65 @blur="validateField('password')" 66 :class="{ 'border-red-500': errors.password }" 67 /> 68 <span v-if="errors.password" class="text-red-500 text-sm">{{ errors.password }}</span> 69 </div> 70 71 <button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded"> 72 Login 73 </button> 74 </form> 75</template>

Form Validation in Astro

astro
1--- 2import { z } from 'zod'; 3 4const loginSchema = z.object({ 5 email: z.email({ error: 'Invalid email address' }), 6 password: z.string().min(8, { error: 'Password must be at least 8 characters' }), 7}); 8 9type LoginForm = z.infer<typeof loginSchema>; 10 11let errors: Partial<Record<keyof LoginForm, string>> = {}; 12let submittedData: LoginForm | null = null; 13 14if (Astro.request.method === 'POST') { 15 const formData = await Astro.request.formData(); 16 const data = { 17 email: formData.get('email'), 18 password: formData.get('password'), 19 }; 20 21 const result = loginSchema.safeParse(data); 22 23 if (!result.success) { 24 result.error.issues.forEach(issue => { 25 const field = issue.path[0] as keyof LoginForm; 26 errors[field] = issue.message; 27 }); 28 } else { 29 submittedData = result.data; 30 // Process form: send to API, create session, etc. 31 } 32} 33--- 34 35{submittedData && ( 36 <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded"> 37 Successfully logged in as {submittedData.email} 38 </div> 39)} 40 41<form method="POST" class="space-y-4"> 42 <div> 43 <input 44 name="email" 45 type="email" 46 placeholder="Email" 47 defaultValue="" 48 class={errors.email ? 'border-red-500' : ''} 49 /> 50 {errors.email && <span class="text-red-500 text-sm">{errors.email}</span>} 51 </div> 52 53 <div> 54 <input 55 name="password" 56 type="password" 57 placeholder="Password" 58 defaultValue="" 59 class={errors.password ? 'border-red-500' : ''} 60 /> 61 {errors.password && <span class="text-red-500 text-sm">{errors.password}</span>} 62 </div> 63 64 <button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded"> 65 Login 66 </button> 67</form>

Server-side Validation (Shared)

For both Vue and Astro, validate on server before processing:

typescript
1// Shared validation utility 2import { z } from 'zod'; 3 4export const loginSchema = z.object({ 5 email: z.email(), 6 password: z.string().min(8), 7}); 8 9export type LoginData = z.infer<typeof loginSchema>; 10 11export async function validateLogin(data: unknown) { 12 return loginSchema.safeParse(data); 13} 14 15// Usage in API endpoint or action 16export async function POST({ request }) { 17 const data = await request.json(); 18 const result = await validateLogin(data); 19 20 if (!result.success) { 21 // Use flattenError for better form handling 22 // Returns: { formErrors: string[], fieldErrors: Record<string, string[]> } 23 return new Response( 24 JSON.stringify({ errors: z.flattenError(result.error) }), 25 { status: 400 } 26 ); 27 } 28 29 // Process validated data 30 return new Response(JSON.stringify({ success: true })); 31}

Client-side Validation with Fetch (Vue)

vue
1<script setup lang="ts"> 2import { ref } from 'vue'; 3 4const email = ref(''); 5const password = ref(''); 6const errors = ref<Record<string, string>>({}); 7const loading = ref(false); 8 9const handleSubmit = async () => { 10 loading.value = true; 11 errors.value = {}; 12 13 try { 14 const response = await fetch('/api/login', { 15 method: 'POST', 16 headers: { 'Content-Type': 'application/json' }, 17 body: JSON.stringify({ email: email.value, password: password.value }), 18 }); 19 20 if (!response.ok) { 21 const { errors: serverErrors } = await response.json(); 22 if (serverErrors?.fieldErrors) { 23 Object.entries(serverErrors.fieldErrors).forEach(([field, messages]) => { 24 const firstMessage = Array.isArray(messages) ? messages[0] : messages; 25 if (firstMessage) { 26 errors.value[field] = firstMessage; 27 } 28 }); 29 } 30 return; 31 } 32 33 // Success: redirect or update state 34 window.location.href = '/dashboard'; 35 } catch (error) { 36 errors.value.submit = 'Network error. Please try again.'; 37 } finally { 38 loading.value = false; 39 } 40}; 41</script> 42 43<template> 44 <form @submit.prevent="handleSubmit" class="space-y-4"> 45 <div v-if="errors.submit" class="text-red-600 text-sm">{{ errors.submit }}</div> 46 47 <div> 48 <input v-model="email" type="email" placeholder="Email" /> 49 <span v-if="errors.email" class="text-red-500 text-sm">{{ errors.email }}</span> 50 </div> 51 52 <div> 53 <input v-model="password" type="password" placeholder="Password" /> 54 <span v-if="errors.password" class="text-red-500 text-sm">{{ errors.password }}</span> 55 </div> 56 57 <button :disabled="loading" type="submit" class="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"> 58 {{ loading ? 'Logging in...' : 'Login' }} 59 </button> 60 </form> 61</template>

Related Skills

Looking for an alternative to zod-4 or building a Community AI Agent? Explore these related open-source MCP Servers.

View All

widget-generator

Logo of f
f

widget-generator is an open-source AI agent skill for creating widget plugins that are injected into prompt feeds on prompts.chat. It supports two rendering modes: standard prompt widgets using default PromptCard styling and custom render widgets built as full React components.

149.6k
0
Design

testing

Logo of lobehub
lobehub

Testing is a process for verifying AI agent functionality using commands like bunx vitest run and optimizing workflows with targeted test runs.

73.3k
0
Communication

chat-sdk

Logo of lobehub
lobehub

chat-sdk is a unified TypeScript SDK for building chat bots across multiple platforms, providing a single interface for deploying bot logic.

73.0k
0
Communication

zustand

Logo of lobehub
lobehub

The ultimate space for work and life — to find, build, and collaborate with agent teammates that grow with you. We are taking agent harness to the next level — enabling multi-agent collaboration, effortless agent team design, and introducing agents as the unit of work interaction.

72.8k
0
Communication