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 --verboseFindings 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
rulesenables it - AST visitors only: project-level
scanrules 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.