# Source: https://docs.convex.dev/eslint.md

# ESLint rules

The Convex ESLint plugin provides linter rules that enforce best practices for Convex functions. Let us know if there's a rule you would find helpful!

## Setup[​](#setup "Direct link to Setup")

For ESLint 9 (flat config, using `eslint.config.js`), install the rules with:

```
npm i @convex-dev/eslint-plugin --save-dev
```

and add this to your `eslint.config.js` file:

```
import { defineConfig } from "eslint/config";

import convexPlugin from "@convex-dev/eslint-plugin";

export default defineConfig([
  // Other configurations

  ...convexPlugin.configs.recommended,
]);
```

If you’re using the deprecated `.eslintrc.js` format

Install these two libraries:

```
npm i @typescript-eslint/eslint-plugin @convex-dev/eslint-plugin --save-dev
```

In `.eslintrc.js`, add:

```
module.exports =
  extends: [
    // Other configurations
    "plugin:@typescript-eslint/recommended",
    "plugin:@convex-dev/recommended",
  ],
  ignorePatterns: ["node_modules/", "dist/", "build/"],
};
```

If your Convex functions are in a directory other than `convex`

By default, the Convex ESLint plugin will only apply rules in the `convex` directory.

If you’re [customizing the Convex directory location](/production/project-configuration.md#changing-the-convex-folder-name-or-location), here’s how to adapt your ESLint configuration:

```
// eslint.config.js
import { defineConfig } from "eslint/config";

import convexPlugin from "@convex-dev/eslint-plugin";

const recommendedConfig = convexPlugin.configs.recommended[0];
const recommendedRules = recommendedConfig.rules;

export default defineConfig([
  // Other configurations go here...

  // Custom configuration with modified directory pattern
  {
    files: ["**/src/convex/**/*.ts"],
    plugins: {
      "@convex-dev": convexPlugin,
    },
    rules: recommendedRules,
  },
]);
```

If you’re using the `next lint` command from Next.js

For `next lint` to run ESLint on your `convex` directory you need to add that directory to the default set of directories. Add this section to your `next.config.ts`:

```
const nextConfig: NextConfig = {
  /* other options here */

  eslint: {
    dirs: ["pages", "app", "components", "lib", "src", "convex"],
  },
};
```

## Rules[​](#rules "Direct link to Rules")

| Rule                                                                                                                                     | Recommended | Auto-fixable |
| ---------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------ |
| [`@convex-dev/no-old-registered-function-syntax`](#no-old-registered-function-syntax)<br />Prefer object syntax for registered functions | ✅          | 🔧           |
| [`@convex-dev/require-argument-validators`](#require-argument-validators)<br />Require argument validators for Convex functions          | ✅          | 🔧           |
| [`@convex-dev/explicit-table-ids`](#explicit-table-ids)<br />Require explicit table names in database operations                         | ✅          | 🔧           |
| [`@convex-dev/import-wrong-runtime`](#import-wrong-runtime)<br />Prevent Convex runtime files from importing from Node runtime files     |             |              |

### no-old-registered-function-syntax[​](#no-old-registered-function-syntax "Direct link to no-old-registered-function-syntax")

Prefer object syntax for registered functions.

Convex queries, mutations, and actions can be defined with a single function or with an object containing a handler property. Using the objects makes it possible to add argument and return value validators, so is always preferable.

```
// ✅ Allowed by this rule:
export const list = query({
  handler: async (ctx) => {
    const data = await ctx.db.query("messages").collect();
    ...
  },
});

// ❌ Not allowed by this rule:
export const list = query(async (ctx) => {
  const data = await ctx.db.query("messages").collect();
  ...
});
```

### require-argument-validators[​](#require-argument-validators "Direct link to require-argument-validators")

Require argument validators for Convex functions.

Convex queries, mutations, and actions can validate their arguments before beginning to run the handler function. Besides being a concise way to validate, the types of arguments, using argument validators enables generating more descriptive function specs and therefore OpenAPI bindings.

```
// ✅ Allowed by this rule:
export const list = query({
  args: {},
  handler: async (ctx) => {
    ...
  },
});

// ✅ Allowed by this rule:
export const list = query({
  args: { channel: v.id('channel') },
  handler: async (ctx, { channel }) => {
    ...
  },
});

// ❌ Not allowed with option { ignoreUnusedArguments: false } (default)
// ✅ Allowed with option { ignoreUnusedArguments: true }
export const list = query({
  handler: async (ctx) => {
    ...
  },
});

// ❌ Not allowed by this rule:
export const list = query({
  handler: async (ctx, { channel }: { channel: Id<"channel"> }) => {
    ...
  },
});
```

This rule can be customized to tolerate functions that don’t define an argument validator but don’t use their arguments. Here’s how you can set up the rule to work this way:

```
// eslint.config.js

export default defineConfig([
  // Your other rules…

  {
    files: ["**/convex/**/*.ts"],
    rules: {
      "@convex-dev/require-args-validator": [
        "error",
        {
          ignoreUnusedArguments: true,
        },
      ],
    },
  },
]);
```

### explicit-table-ids[​](#explicit-table-ids "Direct link to explicit-table-ids")

Require explicit table names in database operations.

Starting from version 1.31.0 of the `convex` npm package, we recommend including the table name as the first argument to database operations (`db.get`, `db.replace`, `db.patch`, `db.delete`).

This approach is more secure because it prevents vulnerabilities when an ID from one table is incorrectly typed as belonging to another table. The implicit syntax (where table names are inferred from the ID) will be deprecated in the future to give developers more control over ID generation. For both these reasons, we recommend developers to migrate to the new format.

This rule helps migrate code from the old implicit format to the new explicit format. It uses TypeScript type information to automatically infer the table name from the `Id<"tableName">` type and provides automatic fixes.

```
const messageId: Id<"messages"> = "123" as Id<"messages">;

// ✅ Allowed by this rule:
const message = await ctx.db.get("messages", messageId);
await ctx.db.patch("messages", messageId, { text: "updated" });
await ctx.db.replace("messages", messageId, {
  text: "replaced",
  author: "Alice",
});
await ctx.db.delete("messages", messageId);

// ❌ Not allowed by this rule:
const message = await ctx.db.get(messageId);
await ctx.db.patch(messageId, { text: "updated" });
await ctx.db.replace(messageId, { text: "replaced", author: "Alice" });
await ctx.db.delete(messageId);
```

typescript-eslint required

In order for this rule to work, [typescript-eslint](https://typescript-eslint.io) must be set up in your ESLint configuration. If typescript-eslint is installed and the rule doesn’t seem to work, please make sure that [type-aware linting](https://typescript-eslint.io/troubleshooting/typed-linting/) is enabled.

Note that if you’re not using ESLint, you can alternatively use the `@convex-dev/codemod` CLI tool to automatically migrate to the new format:

```
npx @convex-dev/codemod@latest explicit-ids
```

[Learn more on news.convex.dev →](https://news.convex.dev/db-table-name/)

### import-wrong-runtime[​](#import-wrong-runtime "Direct link to import-wrong-runtime")

Prevent Convex runtime files from importing from Node runtime files (files with a `"use node"` directive).

This rule is experimental. Please let us know if you find it helpful!

```
// In a file that doesn’t use `"use node"`:

// ✅ Allowed by this rule:
import { someFunction } from "./someOtherFile"; // where someOtherFile doesn't use `"use node"`

// ❌ Not allowed by this rule:
import { someFunction } from "./someNodeFile"; // where someNodeFile uses `"use node"`
```