New
Introducing React Doctor for Enterprise

Add custom rules

Load your own oxlint-shaped plugins into a React Doctor scan. Use this for team conventions or project-specific detectors that do not belong in the core rule set. Custom rules share the same CLI output, JSON report, score, PR comments, and CI gate as built-in rules.

When to use a custom plugin

Use a custom plugin when:

  • You need a detector React Doctor does not ship
  • The rule is team- or product-specific
  • You want findings in the same report as built-in diagnostics

Use the ESLint and oxlint plugins page when you want React Doctor’s built-in rules inside ESLint or oxlint instead.

Write a plugin module

Export meta.name and a rules map. Each rule implements create(context) and returns oxlint visitors:

// lint/team-conventions.cjs
module.exports = {
  meta: {
    name: "team-conventions",
  },
  rules: {
    "no-forbidden-word": {
      create(context) {
        return {
          JSXText(node) {
            if (typeof node.value !== "string") return;
            if (!node.value.includes("FORBIDDEN")) return;
            context.report({
              node,
              message:
                "team policy: FORBIDDEN is not allowed in JSX text",
            });
          },
        };
      },
    },
  },
};

meta.name becomes the plugin namespace in rule keys and diagnostics. Relative paths resolve from the directory that contains your doctor.config.* file, not from the scan root.

You can also point plugins at an npm package that default-exports the same { meta, rules } shape.

Enable the plugin in config

Add the plugin path or package name, then opt in each rule under rules:

{
  "$schema": "https://react.doctor/schema/config.json",
  "plugins": ["./lint/team-conventions.cjs"],
  "rules": {
    "team-conventions/no-forbidden-word": "error"
  }
}

Rule keys use <plugin-name>/<rule-name>. Plugin rules stay off until you set them to error or warn.

Severity, ignore, and surfaces work the same way they do for built-in rules. See Config files.

Run a scan

npx react-doctor@latest --verbose

Findings from your plugin appear with plugin set to meta.name and rule set to the rule id. JSON mode (--json) uses the same fields, so graders and scripts can key on team-conventions/no-forbidden-word.

Load plugins from the Node API

Pass plugins and rules through batch diagnose config, or put them in on-disk doctor.config.* for single-directory diagnose:

import { diagnose } from "react-doctor/api";

const result = await diagnose({
  projects: [{ directory: "./apps/web" }],
  config: {
    plugins: ["./lint/team-conventions.cjs"],
    rules: {
      "team-conventions/no-forbidden-word": "error",
    },
  },
});

Relative plugin paths resolve from the config source directory for that project. See the Node.js API.

Limits of custom plugins

Custom plugins are oxlint visitor rules with these limits:

  • Opt-in only: a plugin rule never runs until rules enables it
  • AST visitors only: project-level scan rules and React Doctor capability gates (requires, disabledWhen) stay built-in
  • Less diagnostic metadata: user-plugin findings often fall back to the plugin and rule ids for title and category; built-in registry rules carry richer titles, categories, and tags
  • Unresolved plugins are skipped: a missing path or package warns and the scan continues without that plugin

If a rule should ship for every React Doctor user, contribute it upstream instead of keeping it as a private plugin.