jpskill.com
🛠️ 開発・MCP コミュニティ

web-forms-zod-validation

TypeScriptでWebフォームのデータを扱う際に、Zodというライブラリを使って、データの形式や内容が正しいか自動でチェックし、型を安全に扱えるようにするSkill。

📜 元の英語説明(参考)

Zod schema validation patterns for TypeScript - schema definitions, type inference, refinements, transforms, discriminated unions

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

一言でいうと

TypeScriptでWebフォームのデータを扱う際に、Zodというライブラリを使って、データの形式や内容が正しいか自動でチェックし、型を安全に扱えるようにするSkill。

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

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

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

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

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

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して web-forms-zod-validation.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → web-forms-zod-validation フォルダができる
  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-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

Zodスキーマ検証パターン

クイックガイド: 信頼境界(APIレスポンス、フォーム入力、設定、URLパラメータ)でランタイム検証にZodを使用します。スキーマを一度定義し、z.inferで型を派生させます。エラー処理にはsafeParse、カスタム検証にはrefine/superRefine、データ変換にはtransformを使用します。すべての検証制限には名前付き定数を使用します。

バージョンに関する注意: Zod v4が安定版リリースとなりました(v4.1+)。これにより、文字列解析が14.7倍高速化、バンドルサイズが57%縮小、新しいトップレベルAPI(z.email()z.url()z.iso.*)が導入されました。v3のメソッドチェーンによる同等の記述(z.string().email())も引き続き動作しますが、非推奨となりました。移行の詳細については、reference.mdを参照してください。


<critical_requirements>

重要: このSkillを使用する前に

すべてのコードはCLAUDE.mdのプロジェクト規約に従う必要があります (kebab-case、名前付きエクスポート、インポート順序、import type、名前付き定数)

(ユーザー向けの検証には、parseの代わりに必ずsafeParseを使用してください - 未処理の例外を防ぎます)

(型を派生させるには、必ずz.infer<typeof schema>を使用してください - スキーマを別のインターフェースとして複製しないでください)

(信頼境界で必ず検証してください - APIレスポンス、フォーム入力、設定ファイル、URLパラメータ)

(検証制限には必ず名前付き定数を使用してください - .min().max().length()にマジックナンバーを使用しないでください)

</critical_requirements>


自動検出: Zodスキーマ、z.object、z.string、z.number、z.infer、safeParse、refine、superRefine、transform、discriminatedUnion、z.coerce、z.pipe、z.catch、z.brand、z.lazy、z.email、z.url、z.iso

使用場面:

  • データを使用する前にAPIレスポンスを検証する
  • 型安全性を確保しながらフォーム入力データを解析する
  • 設定ファイルまたは環境変数を検証する
  • システム間の契約を定義する(フロントエンド/バックエンドで共有するスキーマ)
  • 信頼できないソースからのデータのランタイム型チェック

使用しない場面:

  • 内部関数のパラメータ(信頼できるデータにはTypeScriptで十分です)
  • スキーマ定義を必要としない単純なブール値チェック
  • 検証のオーバーヘッドが問題となるパフォーマンスが重要なホットパス

<philosophy>

哲学

TypeScriptは、制御可能なコードに対してコンパイル時の型安全を提供します。Zodは、制御できないデータ(APIレスポンス、ユーザー入力、設定ファイル、URLパラメータ)に対してランタイム検証を提供します。内部契約にはTypeScriptを使用し、外部データがシステムに入る信頼境界でZodを使用します。

重要な原則: スキーマを一度定義し、型を派生させます。並行して型定義と検証ロジックを維持しないでください - それらは乖離します。

// スキーマが信頼できる情報源
const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

// 型は派生され、常に同期が保たれる
type User = z.infer<typeof UserSchema>;

</philosophy>


<patterns>

コアパターン

パターン1: 名前付き定数を使用したスキーマ定義

すべての検証制限に名前付き定数を使用してスキーマを定義します。ユーザー向けのフィールドにはカスタムエラーメッセージを使用します。

const MIN_USERNAME_LENGTH = 3;
const MAX_USERNAME_LENGTH = 50;

const UserSchema = z.object({
  username: z
    .string()
    .min(
      MIN_USERNAME_LENGTH,
      `Username must be at least ${MIN_USERNAME_LENGTH} characters`,
    )
    .max(
      MAX_USERNAME_LENGTH,
      `Username cannot exceed ${MAX_USERNAME_LENGTH} characters`,
    ),
  email: z.string().email("Invalid email format"),
});

type User = z.infer<typeof UserSchema>; // 常に派生、手動インターフェースは不要

利点: 名前付き定数により制限を発見しやすくなり、カスタムエラーメッセージによりUXが向上し、型がスキーマから派生する

再利用可能なサブスキーマとCRUD構成パターンを含む完全なスキーマの例については、examples/core.mdを参照してください。


パターン2: エラー処理のための安全な解析

ユーザー入力とAPIレスポンスにはsafeParseを使用します。parseは、無効な場合にプログラミングエラーとなる設定/内部データ用に予約します。

const result = UserSchema.safeParse(data);

if (!result.success) {
  const errors = result.error.issues.reduce(
    (acc, err) => {
      const field = err.path.join(".");
      acc[field] = err.message;
      return acc;
    },
    {} as Record<string, string>,
  );
  return { success: false, errors };
}

return { success: true, user: result.data };

利点: safeParseは決して例外をスローせず、検証エラーは明示的に処理され、エラーフォーマットは役立つフィールドレベルのフィードバックを提供します

フォーム検証とAPIレスポンス検証のパターンについては、examples/core.mdを参照してください。


パターン3: Refinementとクロスフィールド検証

カスタム検証ロジックにはrefineを使用します。特定のエラーパスを持つクロスフィールド検証が必要な場合は、superRefineを使用します。

const MIN_PASSWORD_LENGTH = 8;

const PasswordFormSchema = z
  .object({
    password: z.string().min(MIN_PASSWORD_LENGTH),
    confirmPassword: z.string(),
  })
  .superRefine((data, ctx) => {
    if (data.password !== data.confirmPassword) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: "Passwords do not match",
        path: ["confirmPassword"],
      });
    }
  });

利点: superRefineは特定のエラーパスを持つクロスフィールド検証を可能にし、すべての検証をスキーマ内に保持します

パスワードのrefinementチェーンと条件付き検証パターンについては、examples/core.mdを参照してください。


パターン4: Transformと型変換

検証中にデータを変換するにはtransformを使用します。変換によって型が変わる場合は、z.inputz.outputを使用します。

const DateSchema = z
  .string()
  .datetime()
  .transform((str) => new Date(str));

type DateInput = z.input<typeof DateSchema>; // string
type DateOutput = z.output<typeof DateSchema>; // Date

注意点: z.inferは出力型を返します。関数が検証前の入力を受け入れる場合は、パラメータの型にz.inputを使用します。

強制パターン(URLパラメータ、フォームデータ)と変換パイプラインについては、examples/transforms.mdを参照してください。


(原文がここで切り詰められています)

📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Zod Schema Validation Patterns

Quick Guide: Use Zod for runtime validation at trust boundaries (API responses, form inputs, config, URL params). Define schemas once, derive types with z.infer. Use safeParse for error handling, refine/superRefine for custom validation, transform for data conversion. Named constants for all validation limits.

Version Note: Zod v4 is now the stable release (v4.1+). It brings 14.7x faster string parsing, 57% smaller bundle, and new top-level APIs (z.email(), z.url(), z.iso.*). The v3 method-chain equivalents (z.string().email()) still work but are deprecated. For migration details, see reference.md.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use safeParse instead of parse for user-facing validation - prevents unhandled exceptions)

(You MUST use z.infer<typeof schema> to derive types - never duplicate schema as separate interface)

(You MUST validate at trust boundaries - API responses, form inputs, config files, URL params)

(You MUST use named constants for validation limits - NO magic numbers in .min(), .max(), .length())

</critical_requirements>


Auto-detection: Zod schemas, z.object, z.string, z.number, z.infer, safeParse, refine, superRefine, transform, discriminatedUnion, z.coerce, z.pipe, z.catch, z.brand, z.lazy, z.email, z.url, z.iso

When to use:

  • Validating API responses before using data
  • Parsing form input data with type safety
  • Validating configuration files or environment variables
  • Defining contracts between systems (frontend/backend shared schemas)
  • Runtime type checking for data from untrusted sources

When NOT to use:

  • Internal function parameters (TypeScript is sufficient for trusted data)
  • Simple boolean checks that don't need schema definition
  • Performance-critical hot paths where validation overhead matters

<philosophy>

Philosophy

TypeScript provides compile-time type safety for code you control. Zod provides runtime validation for data you don't control - API responses, user input, configuration files, URL parameters. Use TypeScript for internal contracts; use Zod at trust boundaries where external data enters your system.

Key principle: Define the schema once, derive the type. Never maintain parallel type definitions and validation logic - they will drift apart.

// Schema is the source of truth
const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

// Type is derived, always in sync
type User = z.infer<typeof UserSchema>;

</philosophy>


<patterns>

Core Patterns

Pattern 1: Schema Definition with Named Constants

Define schemas with named constants for all validation limits. Custom error messages for user-facing fields.

const MIN_USERNAME_LENGTH = 3;
const MAX_USERNAME_LENGTH = 50;

const UserSchema = z.object({
  username: z
    .string()
    .min(
      MIN_USERNAME_LENGTH,
      `Username must be at least ${MIN_USERNAME_LENGTH} characters`,
    )
    .max(
      MAX_USERNAME_LENGTH,
      `Username cannot exceed ${MAX_USERNAME_LENGTH} characters`,
    ),
  email: z.string().email("Invalid email format"),
});

type User = z.infer<typeof UserSchema>; // Always derived, never manual interface

Why good: named constants make limits discoverable, custom error messages improve UX, type derived from schema

See examples/core.md for complete schema examples with reusable sub-schemas and CRUD composition patterns.


Pattern 2: Safe Parsing for Error Handling

Use safeParse for user input and API responses. Reserve parse for config/internal data where invalid = programming error.

const result = UserSchema.safeParse(data);

if (!result.success) {
  const errors = result.error.issues.reduce(
    (acc, err) => {
      const field = err.path.join(".");
      acc[field] = err.message;
      return acc;
    },
    {} as Record<string, string>,
  );
  return { success: false, errors };
}

return { success: true, user: result.data };

Why good: safeParse never throws, validation errors handled explicitly, error formatting provides useful field-level feedback

See examples/core.md for form validation and API response validation patterns.


Pattern 3: Refinements and Cross-Field Validation

Use refine for custom validation logic. Use superRefine when you need cross-field validation with specific error paths.

const MIN_PASSWORD_LENGTH = 8;

const PasswordFormSchema = z
  .object({
    password: z.string().min(MIN_PASSWORD_LENGTH),
    confirmPassword: z.string(),
  })
  .superRefine((data, ctx) => {
    if (data.password !== data.confirmPassword) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: "Passwords do not match",
        path: ["confirmPassword"],
      });
    }
  });

Why good: superRefine enables cross-field validation with specific error paths, keeps all validation in the schema

See examples/core.md for password refinement chains and conditional validation patterns.


Pattern 4: Transforms and Type Conversion

Use transform to convert data during validation. Use z.input and z.output when transforms change the type.

const DateSchema = z
  .string()
  .datetime()
  .transform((str) => new Date(str));

type DateInput = z.input<typeof DateSchema>; // string
type DateOutput = z.output<typeof DateSchema>; // Date

Gotcha: z.infer returns the output type. When a function accepts pre-validation input, use z.input for the parameter type.

See examples/transforms.md for coercion patterns (URL params, form data) and transform pipelines.


Pattern 5: Discriminated Unions

Use discriminatedUnion when objects share a common discriminator field. Provides better error messages and TypeScript narrowing than union.

const NotificationSchema = z.discriminatedUnion("type", [
  z.object({
    type: z.literal("email"),
    email: z.string().email(),
    subject: z.string(),
  }),
  z.object({ type: z.literal("sms"), phone: z.string(), message: z.string() }),
  z.object({
    type: z.literal("push"),
    deviceId: z.string(),
    title: z.string(),
  }),
]);

Why good over z.union: discriminatedUnion reports which variant failed (not "Invalid input"), TypeScript narrows type in switch statements

See examples/core.md for payment method union and type narrowing examples.


Pattern 6: Schema Composition

Compose schemas using extend, pick, omit, and partial for CRUD operations.

const BaseEntitySchema = z.object({
  id: z.string().uuid(),
  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
});

const UserSchema = BaseEntitySchema.extend({
  email: z.string().email(),
  name: z.string(),
});
const CreateUserSchema = UserSchema.omit({
  id: true,
  createdAt: true,
  updatedAt: true,
});
const UpdateUserSchema = CreateUserSchema.partial();
const UserSummarySchema = UserSchema.pick({ id: true, name: true });

See examples/core.md for full CRUD schema composition example.


Pattern 7: Coercion for String Inputs

Use z.coerce for URL params and form data that arrive as strings. Simpler than manual parsing.

const DEFAULT_PAGE = 1;
const DEFAULT_LIMIT = 20;
const MAX_LIMIT = 100;

const PaginationSchema = z.object({
  page: z.coerce.number().int().positive().default(DEFAULT_PAGE),
  limit: z.coerce
    .number()
    .int()
    .positive()
    .max(MAX_LIMIT)
    .default(DEFAULT_LIMIT),
});

// "3" -> 3, "50" -> 50, missing -> defaults

Gotcha: z.coerce.boolean() coerces any truthy value to true, including the string "false". Use explicit comparison for string booleans.

See examples/transforms.md for complete pagination and query param patterns.


Pattern 8: Optional, Nullable, and Nullish

const ProfileSchema = z.object({
  name: z.string(), // Required
  bio: z.string().optional(), // string | undefined
  avatar: z.string().url().nullable(), // string | null
  nickname: z.string().nullish(), // string | null | undefined
  theme: z.string().default("light"), // string (always defined)
});

Key distinction: nullable = explicitly set to null (API returns null), optional = may be omitted entirely, nullish = either.

</patterns>


Detailed Resources:


<red_flags>

RED FLAGS

High Priority Issues:

  • Using parse for user-facing validation - Throws exceptions for expected invalid input, requiring try-catch and losing detailed error info
  • Magic numbers in validation limits - .min(3).max(50) is undocumented; use named constants like MIN_USERNAME_LENGTH
  • Defining separate TypeScript interfaces - Creates drift between schema and type; always use z.infer<typeof schema>
  • Not validating at trust boundaries - API responses, user input, and config should always be validated at entry points
  • Async refinements with parse instead of parseAsync - Async refinements silently fail with sync parse methods

Medium Priority Issues:

  • Overly strict validation on optional fields - Empty strings should often be treated as undefined for optional fields
  • Missing custom error messages - Default "Invalid input" messages are not user-friendly
  • Validating internal function parameters with Zod - TypeScript is sufficient for trusted internal code
  • Using .passthrough() by default - Allows unexpected fields through; use .strict() when you want to reject extras

Gotchas & Edge Cases:

  • z.coerce.boolean(): Coerces any truthy value to true, including string "false" - use explicit string comparison if needed
  • Transform order: .transform() runs after all other validations; refinements on transformed values need .pipe() to validate after
  • Empty strings: z.string().email() rejects empty strings; use .email().or(z.literal("")) to allow empty
  • Extend with refinements: .extend() on a schema with .refine() throws; apply refinements after extending instead
  • Date parsing: z.coerce.date() uses new Date() which accepts many formats; use .datetime() for strict ISO format
  • z.union vs z.discriminatedUnion: Union tries all schemas and reports combined errors; discriminatedUnion uses discriminator for targeted validation and better errors
  • v4: .refine() function second arg removed: z.string().refine(fn, (val) => ({ message: ... })) no longer works; use superRefine() for dynamic messages
  • v4: ctx.path removed in .superRefine(): No longer available for performance reasons; ctx.addIssue() still works
  • v4 deprecations: .flatten() deprecated - use z.flattenError() instead; .format() deprecated - use z.treeifyError() instead; .merge() deprecated - use .extend() instead

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md

(You MUST use safeParse instead of parse for user-facing validation - prevents unhandled exceptions)

(You MUST use z.infer<typeof schema> to derive types - never duplicate schema as separate interface)

(You MUST validate at trust boundaries - API responses, form inputs, config files, URL params)

(You MUST use named constants for validation limits - NO magic numbers in .min(), .max(), .length())

Failure to follow these rules will create type mismatches, unhandled exceptions, and unmaintainable validation code.

</critical_reminders>