# eslint-plugin-zod

[![CI][CIBadge]][CIURL]
[![Code style: prettier][CodeStyleBadge]][CodeStyleURL]
[![Lint: eslint][lintBadge]][lintURL]
[![npm version][npmVersionBadge]][npmVersionURL]
[![issues][issuesBadge]][issuesURL]

[CIBadge]: https://img.shields.io/github/actions/workflow/status/marcalexiei/eslint-plugin-zod/ci.yml?style=for-the-badge&logo=github&event=push&label=CI
[CIURL]: https://github.com/marcalexiei/eslint-plugin-zod/actions/workflows/CI.yml/badge.svg
[CodeStyleBadge]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=for-the-badge&logo=prettier
[CodeStyleURL]: https://prettier.io
[npmVersionBadge]: https://img.shields.io/npm/v/eslint-plugin-zod.svg?style=for-the-badge&logo=npm
[npmVersionURL]: https://www.npmjs.com/package/eslint-plugin-zod
[lintBadge]: https://img.shields.io/badge/lint-eslint-3A33D1?logo=eslint&style=for-the-badge
[lintURL]: https://eslint.org
[issuesBadge]: https://img.shields.io/github/issues/marcalexiei/eslint-plugin-zod.svg?style=for-the-badge
[issuesURL]: https://github.com/marcalexiei/eslint-plugin-zod/issues

[ESLint](https://eslint.org) plugin that adds custom linting rules to enforce best practices when using [Zod](https://github.com/colinhacks/zod)

## Rules

`zod` and `zod-mini` have distinct API surfaces.
This plugin is primarily built for `zod`, so some rules are exclusive to `zod` and not available for `zod-mini`.

<!-- begin auto-generated rules list -->

💼 Configurations enabled in.\
✅ Set in the `recommended` configuration.\
✔️ Set in the `recommendedMini` configuration.\
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\
❌ Deprecated.

### Universal rules (`zod` & `zod-mini`)

| Name                                                                         | Description                                                                                   | 💼    | 🔧  | 💡  | ❌  |
| :--------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :---- | :-- | :-- | :-- |
| [consistent-import](docs/rules/consistent-import.md)                         | Enforce a consistent import style for Zod                                                     | ✅    | 🔧  |     |     |
| [consistent-import-source](docs/rules/consistent-import-source.md)           | Enforce consistent source from Zod imports                                                    |       |     | 💡  |     |
| [consistent-object-schema-type](docs/rules/consistent-object-schema-type.md) | Enforce consistent usage of Zod schema methods                                                |       |     | 💡  |     |
| [no-any-schema](docs/rules/no-any-schema.md)                                 | Disallow usage of `z.any()` in Zod schemas                                                    | ✅ ✔️ |     | 💡  |     |
| [no-empty-custom-schema](docs/rules/no-empty-custom-schema.md)               | Disallow usage of `z.custom()` without arguments                                              | ✅    |     |     |     |
| [no-unknown-schema](docs/rules/no-unknown-schema.md)                         | Disallow usage of `z.unknown()` in Zod schemas                                                |       |     |     |     |
| [prefer-meta](docs/rules/prefer-meta.md)                                     | Enforce usage of `.meta()` over `.describe()`                                                 | ✅ ✔️ | 🔧  |     |     |
| [prefer-namespace-import](docs/rules/prefer-namespace-import.md)             | Enforce importing zod as a namespace import (`import * as z from 'zod'`)                      |       | 🔧  |     | ❌  |
| [require-brand-type-parameter](docs/rules/require-brand-type-parameter.md)   | Require type parameter on `.brand()` functions                                                | ✅ ✔️ |     | 💡  |     |
| [require-error-message](docs/rules/require-error-message.md)                 | Enforce that custom refinements include an error message                                      | ✅ ✔️ | 🔧  |     |     |
| [require-schema-suffix](docs/rules/require-schema-suffix.md)                 | Require schema suffix when declaring a Zod schema                                             | ✅ ✔️ |     |     |     |
| [schema-error-property-style](docs/rules/schema-error-property-style.md)     | Enforce consistent style for error messages in Zod schema validation (using ESQuery patterns) |       |     |     |     |

### `zod` exclusive rules

| Name                                                                               | Description                                                                       | 💼  | 🔧  | 💡  | ❌  |
| :--------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :-- | :-- | :-- | :-- |
| [array-style](docs/rules/array-style.md)                                           | Enforce consistent Zod array style                                                | ✅  | 🔧  |     |     |
| [no-number-schema-with-int](docs/rules/no-number-schema-with-int.md)               | Disallow usage of `z.number().int()` as it is considered legacy                   | ✅  | 🔧  |     |     |
| [no-optional-and-default-together](docs/rules/no-optional-and-default-together.md) | Disallow using both `.optional()` and `.default()` on the same Zod schema         | ✅  | 🔧  |     |     |
| [no-string-schema-with-uuid](docs/rules/no-string-schema-with-uuid.md)             | Disallow usage of `z.string().uuid()` in favor of the dedicated `z.uuid()` schema | ✅  | 🔧  |     |     |
| [no-throw-in-refine](docs/rules/no-throw-in-refine.md)                             | Disallow throwing errors directly inside Zod refine callbacks                     | ✅  |     |     |     |
| [prefer-enum-over-literal-union](docs/rules/prefer-enum-over-literal-union.md)     | Prefer `z.enum()` over `z.union()` when all members are string literals.          | ✅  | 🔧  |     |     |
| [prefer-meta-last](docs/rules/prefer-meta-last.md)                                 | Enforce `.meta()` as last method                                                  | ✅  | 🔧  |     |     |
| [prefer-string-schema-with-trim](docs/rules/prefer-string-schema-with-trim.md)     | Enforce `z.string().trim()` to prevent accidental leading/trailing whitespace     | ✅  | 🔧  |     |     |

<!-- end auto-generated rules list -->

## Installation

Install `eslint` and `eslint-plugin-zod` using your preferred package manager:

```shell
npm i --save-dev eslint eslint-plugin-zod
```

```shell
yarn add --dev eslint eslint-plugin-zod
```

```shell
pnpm add --save-dev eslint eslint-plugin-zod
```

## Configuration

1. Import the plugin

   ```ts
   import eslintPluginZod from 'eslint-plugin-zod';
   ```

2. Add `recommended` config to your ESLint setup

   ```ts
   eslintPluginZod.configs.recommended,
   ```

Here’s a minimal example using the flat config format:

```ts
// eslint.config.js
import { defineConfig } from 'eslint/config';
import eslint from '@eslint/js';
import eslintPluginZod from 'eslint-plugin-zod';

export default defineConfig(
  eslint.configs.recommended,
  eslintPluginZod.configs.recommended,
);
```

## Zod peer dependency version

`eslint-plugin-zod` is designed for projects that use `zod@^4`.
While the plugin analyzes Zod schemas in your code,
it doesn’t import or depend on Zod at runtime.
To document this relationship without forcing installation,
Zod is declared as an optional peer dependency in the plugin’s `package.json`.

If your project uses Zod v4, the plugin will automatically lint your schemas.
If you’re not using Zod (for example, in a separate ESLint workspace), you don’t need to install it.
