Convex ESLint Compliance
Write all Convex functions to pass @convex-dev/eslint-plugin. These rules prevent common bugs, security issues, and ensure code quality.
Documentation Sources
Setup
Install the plugin:
npm i @convex-dev/eslint-plugin --save-dev
Configure eslint.config.js :
import { defineConfig } from "eslint/config"; import convexPlugin from "@convex-dev/eslint-plugin";
export default defineConfig([...convexPlugin.configs.recommended]);
Rules
- no-old-registered-function-syntax
Always use object syntax with a handler property.
// Correct export const list = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("messages").collect(); }, });
// Wrong - bare function syntax export const list = query(async (ctx) => { return await ctx.db.query("messages").collect(); });
- require-argument-validators
Always include args object, even when empty.
// Correct - with arguments export const get = query({ args: { id: v.id("messages") }, handler: async (ctx, { id }) => { return await ctx.db.get("messages", id); }, });
// Correct - no arguments export const listAll = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("messages").collect(); }, });
// Wrong - missing args export const get = query({ handler: async (ctx, { id }: { id: Id<"messages"> }) => { return await ctx.db.get("messages", id); }, });
- explicit-table-ids
Use explicit table names in all database operations (Convex 1.31.0+).
// Correct 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);
// Wrong - implicit table from ID type 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);
Migration codemod available:
npx @convex-dev/codemod@latest explicit-ids
- import-wrong-runtime
Never import Node.js runtime files into Convex runtime files.
// convex/queries.ts (no "use node" directive)
// Correct - importing from Convex runtime file import { helper } from "./utils"; // utils.ts has no "use node"
// Wrong - importing from Node runtime file import { nodeHelper } from "./nodeUtils"; // nodeUtils.ts has "use node"
Best Practices
-
Run ESLint before committing: npx eslint convex/
-
Use auto-fix for quick migrations: npx eslint convex/ --fix
-
Add to CI pipeline to catch violations early
-
Configure your editor for real-time feedback
Quick Reference
Rule What it enforces
no-old-registered-function-syntax
Object syntax with handler
require-argument-validators
args: {} on all functions
explicit-table-ids
Table name in db operations
import-wrong-runtime
No Node imports in Convex runtime
References
-
ESLint Plugin Docs
-
Explicit IDs Announcement