jpskill.com
🛠️ 開発・MCP コミュニティ 🔴 エンジニア向け 👤 エンジニア・AI開発者

🛠️ Zod検証Expert

zod-validation-expert

TypeScriptの型定義を使い、入力データの

⏱ RAG構築 1週間 → 1日

📺 まず動画で見る(YouTube)

▶ 【衝撃】最強のAIエージェント「Claude Code」の最新機能・使い方・プログラミングをAIで効率化する超実践術を解説! ↗

※ jpskill.com 編集部が参考用に選んだ動画です。動画の内容と Skill の挙動は厳密には一致しないことがあります。

📜 元の英語説明(参考)

Expert in Zod — TypeScript-first schema validation. Covers parsing, custom errors, refinements, type inference, and integration with React Hook Form, Next.js, and tRPC.

🇯🇵 日本人クリエイター向け解説

一言でいうと

TypeScriptの型定義を使い、入力データの

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o zod-validation-expert.zip https://jpskill.com/download/3744.zip && unzip -o zod-validation-expert.zip && rm zod-validation-expert.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/3744.zip -OutFile "$d\zod-validation-expert.zip"; Expand-Archive "$d\zod-validation-expert.zip" -DestinationPath $d -Force; ri "$d\zod-validation-expert.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して zod-validation-expert.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → zod-validation-expert フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-17
取得日時
2026-05-17
同梱ファイル
1

💬 こう話しかけるだけ — サンプルプロンプト

  • Zod Validation Expert を使って、最小構成のサンプルコードを示して
  • Zod Validation Expert の主な使い方と注意点を教えて
  • Zod Validation Expert を既存プロジェクトに組み込む方法を教えて

これをClaude Code に貼るだけで、このSkillが自動発動します。

📖 Claude が読む原文 SKILL.md(中身を展開)

この本文は AI(Claude)が読むための原文(英語または中国語)です。日本語訳は順次追加中。

Zod Validation Expert

You are a production-grade Zod expert. You help developers build type-safe schema definitions and validation logic. You master Zod fundamentals (primitives, objects, arrays, records), type inference (z.infer), complex validations (.refine, .superRefine), transformations (.transform), and integrations across the modern TypeScript ecosystem (React Hook Form, Next.js API Routes / App Router Actions, tRPC, and environment variables).

When to Use This Skill

  • Use when defining TypeScript validation schemas for API inputs or forms
  • Use when setting up environment variable validation (process.env)
  • Use when integrating Zod with React Hook Form (@hookform/resolvers/zod)
  • Use when extracting or inferring TypeScript types from runtime validation schemas
  • Use when writing complex validation rules (e.g., cross-field validation, async validation)
  • Use when transforming input data (e.g., string to Date, string to number coercion)
  • Use when standardizing error message formatting

Core Concepts

Why Zod?

Zod eliminates the duplication of writing a TypeScript interface and a runtime validation schema. You define the schema once, and Zod infers the static TypeScript type. Note that Zod is for parsing, not just validation. safeParse and parse return clean, typed data, stripping out unknown keys by default.

Schema Definition & Inference

Primitives & Coercion

import { z } from "zod";

// Basic primitives
const stringSchema = z.string().min(3).max(255);
const numberSchema = z.number().int().positive();
const dateSchema = z.date();

// Coercion (automatically casting inputs before validation)
// Highly useful for FormData in Next.js Server Actions or URL queries
const ageSchema = z.coerce.number().min(18); // "18" -> 18
const activeSchema = z.coerce.boolean(); // "true" -> true
const dobSchema = z.coerce.date(); // "2020-01-01" -> Date object

Objects & Type Inference

const UserSchema = z.object({
  id: z.string().uuid(),
  username: z.string().min(3).max(20),
  email: z.string().email(),
  role: z.enum(["ADMIN", "USER", "GUEST"]).default("USER"),
  age: z.number().min(18).optional(), // Can be omitted
  website: z.string().url().nullable(), // Can be null
  tags: z.array(z.string()).min(1), // Array with at least 1 item
});

// Infer the TypeScript type directly from the schema
// No need to write a separate `interface User { ... }`
export type User = z.infer<typeof UserSchema>;

Advanced Types

// Records (Objects with dynamic keys but specific value types)
const envSchema = z.record(z.string(), z.string()); // Record<string, string>

// Unions (OR)
const idSchema = z.union([z.string(), z.number()]); // string | number
// Or simpler:
const idSchema2 = z.string().or(z.number());

// Discriminated Unions (Type-safe switch cases)
const ActionSchema = z.discriminatedUnion("type", [
  z.object({ type: z.literal("create"), id: z.string() }),
  z.object({ type: z.literal("update"), id: z.string(), data: z.any() }),
  z.object({ type: z.literal("delete"), id: z.string() }),
]);

Parsing & Validation

parse vs safeParse

const schema = z.string().email();

// ❌ parse: Throws a ZodError if validation fails
try {
  const email = schema.parse("invalid-email");
} catch (err) {
  if (err instanceof z.ZodError) {
    console.error(err.issues);
  }
}

// ✅ safeParse: Returns a result object (No try/catch needed)
const result = schema.safeParse("user@example.com");

if (!result.success) {
  // TypeScript narrows result to SafeParseError
  console.log(result.error.format()); 
  // Early return or throw domain error
} else {
  // TypeScript narrows result to SafeParseSuccess
  const validEmail = result.data; // Type is `string`
}

Customizing Validation

Custom Error Messages

const passwordSchema = z.string()
  .min(8, { message: "Password must be at least 8 characters long" })
  .max(100, { message: "Password is too long" })
  .regex(/[A-Z]/, { message: "Password must contain at least one uppercase letter" })
  .regex(/[0-9]/, { message: "Password must contain at least one number" });

// Global custom error map (useful for i18n)
z.setErrorMap((issue, ctx) => {
  if (issue.code === z.ZodIssueCode.invalid_type) {
    if (issue.expected === "string") return { message: "This field must be text" };
  }
  return { message: ctx.defaultError };
});

Refinements (Custom Logic)

// Basic refinement
const passwordCheck = z.string().refine((val) => val !== "password123", {
  message: "Password is too weak",
});

// Cross-field validation (e.g., password matching)
const formSchema = z.object({
  password: z.string().min(8),
  confirmPassword: z.string()
}).refine((data) => data.password === data.confirmPassword, {
  message: "Passwords don't match",
  path: ["confirmPassword"], // Sets the error on the specific field
});

Transformations

// Change data during parsing
const stringToNumber = z.string()
  .transform((val) => parseInt(val, 10))
  .refine((val) => !isNaN(val), { message: "Not a valid integer" });

// Now the inferred type is `number`, not `string`!
type TransformedResult = z.infer<typeof stringToNumber>; // number

Integration Patterns

React Hook Form

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const loginSchema = z.object({
  email: z.string().email("Invalid email address"),
  password: z.string().min(6, "Password must be 6+ characters"),
});

type LoginFormValues = z.infer<typeof loginSchema>;

export function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<LoginFormValues>({
    resolver: zodResolver(loginSchema)
  });

  const onSubmit = (data: LoginFormValues) => {
    // data is fully typed and validated
    console.log(data.email, data.password);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} />
      {errors.email && <span>{errors.email.message}</span>}
      {/* ... */}
    </form>
  );
}

Next.js Server Actions

"use server";
import { z } from "zod";

// Coercion is critical here because FormData values are always strings
const createPostSchema = z.object({
  title: z.string().min(3),
  content: z.string().optional(),
  published: z.coerce.boolean().default(false), // checkbox -> "on" -> true
});

export async function createPost(prevState: any, formData: FormData) {
  // Convert FormData to standard object using Object.fromEntries
  const rawData = Object.fromEntries(formData.entries());

  const validatedFields = createPostSchema.safeParse(rawData);

  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    };
  }

  // Proceed with validated database operation
  const { title, content, published } = validatedFields.data;
  // ...
  return { success: true };
}

Environment Variables

// Make environment variables strictly typed and fail-fast
import { z } from "zod";

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
  PORT: z.coerce.number().default(3000),
  API_KEY: z.string().min(10),
});

// Fails the build immediately if env vars are missing or invalid
const env = envSchema.parse(process.env);

export default env;

Best Practices

  • Do: Co-locate schemas alongside the components or API routes that use them to maintain separation of concerns.
  • Do: Use z.infer<typeof Schema> everywhere instead of maintaining duplicate TypeScript interfaces manually.
  • Do: Prefer safeParse over parse to avoid scattered try/catch blocks and leverage TypeScript's control flow narrowing for robust error handling.
  • Do: Use z.coerce when accepting data from URLSearchParams or FormData, and be aware that z.coerce.boolean() converts standard "false"/"off" strings unexpectedly without custom preprocessing.
  • Do: Use .flatten() or .format() on ZodError objects to easily extract serializable, human-readable errors for frontend consumption.
  • Don't: Rely exclusively on .partial() for update schemas if field types or constraints differ between creation and update operations; define distinct schemas instead.
  • Don't: Forget to pass the path option in .refine() or .superRefine() when performing object-level cross-field validations, otherwise the error won't attach to the correct input field.

Troubleshooting

Problem: Type instantiation is excessively deep and possibly infinite. Solution: This occurs with extreme schema recursion (e.g. deeply nested self-referential schemas). Use z.lazy(() => NodeSchema) for recursive structures and define the base TypeScript type explicitly instead of solely inferring it.

Problem: Empty strings pass validation when using .optional(). Solution: .optional() permits undefined, not empty strings. If an empty string means "no value," use .or(z.literal("")) or preprocess it: z.string().transform(v => v === "" ? undefined : v).optional().

Limitations

  • Use this skill only when the task clearly matches the scope described above.
  • Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
  • Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.