一键导入
zod-v4
Use this skill when validating untrusted inputs with Zod v4 (Server Actions, Route Handlers, forms), formatting errors, or migrating Zod v3 → v4.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use this skill when validating untrusted inputs with Zod v4 (Server Actions, Route Handlers, forms), formatting errors, or migrating Zod v3 → v4.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use this skill when you need to diagnose and fix React 19 performance issues (render churn, slow updates, Suspense/Transitions, memoization, profiling, and React Compiler).
Write clean, behavior-focused unit/integration tests that remain trustworthy when code is written or modified by agents (prevents “test weakening”, enforces invariants, and adds CI guardrails).
Use this skill when you are creating or updating Agent Skills in your project (structure, YAML frontmatter, naming, and progressive disclosure).
Use this skill when designing or refactoring boundaries, responsibilities, and change drivers using practical software architecture “hard parts” thinking + SRP.
Use this skill when you need a Bun runbook (dev, build, lint, typecheck, tests, migrations, and verification steps).
Use this skill when you need a thorough code hygiene pass (dead code, lint escapes, consistency, formatting drift, and subtle smells) on touched files.
| name | zod-v4 |
| description | Use this skill when validating untrusted inputs with Zod v4 (Server Actions, Route Handlers, forms), formatting errors, or migrating Zod v3 → v4. |
Use this skill whenever you:
This repo is tenant-scoped: never trust client-provided IDs. Always derive workspace_id server-side.
unknown (or minimally typed input), validate with Zod, then use parsed.data.safeParse / safeParseAsync in server actions to avoid throwing across RSC boundaries.reportInput: true in production unless you have a clear reason.error param (string or function)z.treeifyError(), z.flattenError(), z.prettifyError()Use Zod to validate inputs, then enforce tenancy with requireWorkspaceCached().
unknown where input crosses trust boundaries.Example:
"use server";
import * as z from "zod";
import { requireWorkspaceCached } from "@/lib/workspace";
import type { ActionResult } from "@/lib/actions/action-result";
import { AppError } from "@/lib/errors/codes";
const createClientSchema = z.object({
name: z.string().min(1, { error: "Name is required" }).max(255),
// Zod v4 prefers top-level string formats
email: z.email().max(255).optional(),
});
export async function createClient(
input: unknown
): Promise<ActionResult<{ id: string }>> {
const { workspace } = await requireWorkspaceCached();
const parsed = createClientSchema.safeParse(input);
if (!parsed.success) {
return {
status: "error",
code: AppError.VALIDATION_FAILED,
message: "Invalid input",
messageKey: "Errors.validationFailed",
// For full, UI-friendly field/form errors, prefer the shared helper:
// `lib/validation/zod-action-errors.ts`
};
}
// Always scope DB writes/reads with workspace.id
// ...insert into db with workspace.id...
return { status: "ok", data: { id: "..." } };
}
For “flat” forms, prefer z.flattenError():
const result = createClientSchema.safeParse(input);
if (!result.success) {
const flat = z.flattenError(result.error);
// flat.formErrors: string[]
// flat.fieldErrors: Record<string, string[]>
}
For nested structures (arrays/objects), prefer z.treeifyError():
const tree = z.treeifyError(result.error);
// tree.errors at root, then tree.properties?.fieldName?.errors, etc.
schema.parse(value) throws ZodError.schema.safeParse(value) returns { success: true, data } | { success: false, error }.parseAsync/safeParseAsync if you use async refine or async transforms/codecs.Prefer:
z.infer<typeof schema> (output type)z.input<typeof schema> / z.output<typeof schema> when input/output diverge (pipes, transforms, defaults, coercion)z.coerce.*) in v4In Zod v4 the input type of coerced schemas is unknown by default. If you want a narrower input type, pass a generic:
const amount = z.coerce.number<number>();
// z.input<typeof amount> is number
Zod v4 promotes formats to top-level constructors (tree-shakable and concise). Prefer:
z.email() instead of z.string().email() (method form is deprecated)z.uuid({ version: "v7" }) or z.uuidv7()z.url(), z.httpUrl(), z.ipv4(), z.ipv6(), z.jwt(), etc.Prefer explicit constructors:
z.strictObject({ ... }) (error on unknown keys)z.looseObject({ ... }) (passthrough unknown keys)z.object({ ... }) (default behavior strips unknown keys)message → errorMost APIs now standardize on the error param:
z.string().min(5, { error: "Too short" });
The old { message: "..." } still works but is deprecated.
invalid_type_error / required_error droppedUse an error(issue) => ... function instead, commonly checking issue.input === undefined.
ZodError.format() / ZodError.flatten() are deprecatedz.treeifyError(err) / z.flattenError(err) insteadz.nativeEnum() deprecatedUse z.enum(MyEnumLike) (Zod v4 overload) instead.
z.record() behavior changedz.record(schema) is not supported in v4z.record(keySchema, valueSchema) requiredz.partialRecord(...) to allow missing keys.default() vs .prefault().default() now short-circuits parsing on undefined, so the default value must match the schema’s output..prefault() when you want “default before parsing” behavior (old v3 style).z.promise() deprecatedIf something might be a promise: await it first, then parse.
z.function() changedz.function() is now a function factory (not a schema). Define input and output up-front; use .implementAsync() for async.
z.stringbool() for env-style booleansz.templateLiteral([...]) for template literal types (e.g. ${number}px)z.codec, z.encode, z.decode) for bidirectional transforms (great at network boundaries)z.toJSONSchema(schema, opts); note unrepresentable types and io: "input" vs output modez.string({ error: "Not a string" });
z.string({
error: (issue) => (issue.input === undefined ? "Required" : "Invalid input"),
});
z.config({
customError: (issue) => {
// global default
return "Invalid input";
},
});
Zod v4 supports locales via z.config(z.locales.<lang>()).
In Freelancerino, keep in mind we already have next-intl; decide whether Zod messages should be localized server-side or mapped to translation keys.
workspace_id in every query/mutation.Some files currently use Zod v3-style APIs (e.g. { message: ... } and z.string().email(...)). When touching those files, migrate to v4 style:
{ message: "..." } → { error: "..." }z.string().email() → z.email()err.flatten() / err.format() → z.flattenError(err) / z.treeifyError(err)workspace_id always applied)safeParse in tests so you can assert on issues.Reference: .github/skills/nextjs-cost-performance/SKILL.md
Zod docs (v4):
Freelancerino anchors:
app/actions/clients/mutations.tslib/validation/zod-action-errors.tslib/workspace.tsdb/schema.ts