zod
Zod
CRITICAL: Your training data for Zod is unreliable. APIs change between versions and your memorized patterns may be wrong or deprecated. You MUST fetch and read the live documentation before writing any code. Never assume — verify against current docs first.
Zod is a TypeScript-first schema validation library with automatic static type inference. Define a schema once and get both runtime validation and compile-time types from the same source.
Version note: These best practices target Zod v4 (stable, installed via
npm install zod). The top-level error utilities (z.flattenError(),z.prettifyError(),z.treeifyError()) are v4-only — in v3 these were instance methods (error.flatten()). See the migration guide when upgrading.
Documentation
- Docs: https://zod.dev/llms.txt
Key Capabilities
Zod has built-ins for things developers commonly reach for external packages to handle:
- String format validation: built-in validators for email, URL, UUID, CUID, datetime (ISO 8601), IP addresses, MAC addresses, JWTs, and hashes — no separate validator package needed
- Coercion: use
z.coerce.number()/z.coerce.string()etc. to automatically cast inputs (e.g. form strings to numbers) — no manual casting step required - Transforms:
.transform()on any schema converts parsed values in one step — no need for a separate transformation layer - Error formatting: use
z.treeifyError(),z.prettifyError(), orz.flattenError()to formatZodErrorinto user-friendly shapes — no custom error mappers needed - Bidirectional codecs:
z.pipe()and the codec pattern (encode+decode) handle round-trip transformations (e.g.stringToNumber,base64ToBytes) without external codec libraries
Best Practices
- Prefer
.safeParse()over.parse()in application code..parse()throws aZodErroron invalid input;.safeParse()returns{ success, data, error }and lets you handle failure without try/catch. Reserve.parse()only where an unhandled throw is intentional (e.g. startup config validation). - Infer types from schemas, not the other way around. Use
z.infer<typeof MySchema>to derive TypeScript types — do not write a separate interface and then try to match a schema to it. Keeping the schema as the single source of truth prevents drift. .transform()changes the output type invisibly. After a.transform(), the schema's output type differs from its input type. Calling.parse()gives the transformed value, but.safeParse().dataalso reflects the transform. Passing a transformed schema where an unmodified type is expected is a common source of subtle type errors.- Use
.superRefine()instead of multiple chained.refine()calls when issues must be cross-field. Each.refine()adds a separate validation pass;.superRefine()gives you access to the fullctxso you can attach multiple issues with precise paths in one pass, which produces more precise error messages on objects. - Refinements run after transforms. If you attach a
.refine()to a schema that also has a.transform(), the refinement receives the transformed (output) value, not the raw input. This is non-obvious when migrating from Joi or Yup, where refinements typically operate on raw input. z.coerce.*convertsnullto0andundefinedtoNaN.z.coerce.number()will coercenull→0andundefined→NaNrather than failing validation. For optional form fields where empty should mean absent (not zero), usez.preprocess()instead ofz.coerce.
More from mikkelkrogsholm/dev-skills
meilisearch
Meilisearch — fast, open-source search engine with typo tolerance, faceted search, and AI-powered hybrid search. Use when building with Meilisearch or asking about its index configuration, search parameters, filters, facets, API keys, geosearch, ranking rules, or integration with JavaScript/TypeScript clients. Fetch live documentation for up-to-date details.
41shadcn-ui
shadcn/ui — copy-owned React component library built on Radix UI and Tailwind CSS. Use when building with shadcn/ui or asking about its components, CLI, theming, configuration, or integration with Next.js, Vite, Remix, or other frameworks. Fetch live documentation for up-to-date details.
10bun
Bun — fast all-in-one JavaScript/TypeScript runtime, package manager, bundler, and test runner. Use when building with Bun, running TypeScript, managing packages with bun install, writing tests with bun test, or asking about Bun APIs, configuration, or Node.js migration. Fetch live documentation for up-to-date API details.
9better-auth
Better Auth — framework-agnostic authentication and authorization framework for TypeScript. Use when building with Better Auth or asking about its APIs, configuration, plugins, session management, OAuth, or integration. Fetch live documentation for up-to-date details.
7react
React — JavaScript library for building user interfaces with components. Use when building with React or asking about hooks, state management, effects, Server Components, Suspense, or any React APIs, patterns, or configuration. Fetch live documentation for up-to-date details.
7vite
Vite — next-generation frontend build tool with instant dev server and optimized production builds. Use when building with Vite or asking about its APIs, configuration, plugins, SSR, environment variables, or integration with frameworks. Fetch live documentation for up-to-date details.
6