All skills
waynesutton avatar

/convex-best-practices

@82d1ce2

Production patterns for Convex apps and the rules the @convex-dev/eslint-plugin enforces: validators, indexes, idempotent mutations, avoiding OCC conflicts, thin function wrappers, error handling. Use when reviewing Convex code, asking whether a pattern is right, setting up ESLint, or fixing write conflicts and slow queries.

Use this Skill: https://skilld.dev/gh/waynesutton/convexskills/convex-best-practices

This session only. Nothing lands on disk.

referenceseslint-setup.md

≈1.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

ESLint setup for Convex

Install and configure @convex-dev/eslint-plugin so the rules in the best practices skill fail at lint time instead of in production.

Install

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

For type aware rules (needed by explicit-table-ids) also install typescript-eslint:

npm i --save-dev typescript-eslint

Flat config (ESLint 9)

Minimal:

// eslint.config.js
import { defineConfig } from "eslint/config";
import convexPlugin from "@convex-dev/eslint-plugin";

export default defineConfig([...convexPlugin.configs.recommended]);

With TypeScript rules that catch the two bugs TypeScript itself misses in Convex code, missing awaits and stray any:

// eslint.config.js
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";
import convexPlugin from "@convex-dev/eslint-plugin";

export default defineConfig([
  ...tseslint.configs.recommendedTypeChecked,
  ...convexPlugin.configs.recommended,
  {
    files: ["**/*.ts", "**/*.tsx"],
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      "@typescript-eslint/no-floating-promises": "error",
      "@typescript-eslint/no-misused-promises": "error",
      "@typescript-eslint/no-explicit-any": "warn",
      "@typescript-eslint/no-unused-vars": [
        "error",
        { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
      ],
    },
  },
  { ignores: ["convex/_generated/**", "dist/**", "node_modules/**"] },
]);

no-floating-promises is the one to care about most. A missing await on ctx.db.patch or ctx.scheduler.runAfter can drop a write with no error.

Legacy config (.eslintrc.js)

npm i --save-dev @typescript-eslint/eslint-plugin @convex-dev/eslint-plugin
module.exports = {
  extends: [
    "plugin:@typescript-eslint/recommended",
    "plugin:@convex-dev/recommended",
  ],
  ignorePatterns: ["node_modules/", "dist/", "build/"],
};

Rules in the plugin

Rule In recommended Fixable What it catches
no-old-registered-function-syntax Yes Yes query(async (ctx) => ...) instead of query({ handler })
require-argument-validators Yes Yes Functions with no args
explicit-table-ids Yes Yes ctx.db.get(id) without a table name (see note)
no-filter-in-query Yes No .filter() on a database query
no-top-of-hour-crons Yes No Crons scheduled at minute 0, when load spikes
import-wrong-runtime No No Default runtime files importing from "use node" files
no-collect-in-query No No .collect() in a query where .take() or .paginate() fits

Turn on the two opt in rules in projects with large tables or mixed runtimes:

{
  files: ["convex/**/*.ts"],
  rules: {
    "@convex-dev/import-wrong-runtime": "error",
    "@convex-dev/no-collect-in-query": "warn",
  },
}

Note on explicit-table-ids: the rule wants ctx.db.get("tasks", id) and ctx.db.patch("tasks", id, fields), a form available since convex 1.31. The skills in this repo use ctx.db.get(id). Pick one style per codebase. If you keep the implicit form, turn the rule off; if you move to the explicit form, npx @convex-dev/codemod@latest explicit-ids rewrites the calls for you.

Allow functions that take no arguments to skip args:

{
  files: ["convex/**/*.ts"],
  rules: {
    "@convex-dev/require-argument-validators": [
      "error",
      { ignoreUnusedArguments: true },
    ],
  },
}

Custom convex directory

The plugin only applies to convex/ by default. For src/convex/:

import { defineConfig } from "eslint/config";
import convexPlugin from "@convex-dev/eslint-plugin";

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

export default defineConfig([
  {
    files: ["**/src/convex/**/*.ts"],
    plugins: { "@convex-dev": convexPlugin },
    rules: recommendedRules,
  },
]);

For next lint, add "convex" to eslint.dirs in next.config.ts.

Scripts

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "typecheck": "tsc --noEmit",
    "check": "npm run lint && npm run typecheck"
  }
}

Run npm run check in CI before npx convex deploy. For a pre commit hook, lint-staged with eslint --fix on *.{ts,tsx} keeps commits fast.

Disabling a rule on one line

// eslint-disable-next-line @convex-dev/no-collect-in-query
const all = await ctx.db.query("settings").collect();

Only do this when the table is bounded by design, such as a settings table with a handful of rows.

Docs

Source: SKILL.md on GitHub

1 warning18d5 checks · Risk SAFE
  • Gen Agent Trust Hub18d

    This skill provides production-ready guidelines and best practices for building Convex applications. It is safe and follows industry standards for development tools and documentation.

  • Socket18d

    No alerts

  • Snyk18d

    Risk: MEDIUM · 1 issue

  • Runlayer7mo

    2 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 82d1ce2. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 4 days ago.

Activeupdated 5 days ago

README badge

README badge for waynesutton/convexskills/convex-best-practices

Teaches patterns for building production Convex applications: function organization by domain, argument and return validation, query indexing, error handling with ConvexError, idempotent mutations, and TypeScript usage. Includes the @convex-dev/eslint-plugin for enforcing Convex function syntax and validator rules at build time.

Generated from the current SKILL.md.

Does this skill cover TypeScript usage in Convex?
Yes. The skill includes guidance on leveraging end-to-end type safety with TypeScript, covering the Id and Doc types from the generated dataModel, and best practices for type annotations in functions.
What does the @convex-dev/eslint-plugin enforce?
The plugin enforces four rules: no old registered function syntax, required argument validators on all functions, explicit table names in db operations, and prevention of Node imports in the Convex runtime.
How does this skill address write conflicts?
The skill provides patterns for Convex's optimistic concurrency control: making mutations idempotent, patching directly without reading first when possible, and using Promise.all for parallel independent updates.
Does this skill include examples?
Yes. The skill includes a complete CRUD pattern example, query patterns with indexes, error handling with ConvexError, and patterns for internal vs public functions.
What is the Zen of Convex philosophy covered in this skill?
The skill outlines five core principles: Convex manages caching and consistency, functions are your API, schema is the source of truth, TypeScript should be used everywhere, and queries are reactive subscriptions.

Generated from the current SKILL.md. These answers refresh after source changes.