# Config files

Configure React Doctor with a `doctor.config.*` file or the `reactDoctor` key in `package.json`. This page explains where React Doctor looks, which config wins, and how each common option changes a scan.

## Where React Doctor looks

React Doctor starts in the scanned directory and walks up until it reaches `.git` or a monorepo root. Put `doctor.config.ts` or `doctor.config.json` next to the project you want to scan. You can also put the same object under `reactDoctor` in `package.json`.

Use one config per directory. If more than one exists, React Doctor uses the first valid config it finds and ignores lower-priority fallbacks. Local config wins over ancestor config.

JSON config files use JSON5, so comments and trailing commas are allowed. Add `$schema: "https://react.doctor/schema/config.json"` for editor autocomplete.

Command-line flags override config values.

## Example config file

```ts
// doctor.config.ts
import { defineConfig } from "react-doctor/api";

export default defineConfig({
  ignore: {
    rules: ["react-doctor/no-danger"],
    files: ["src/generated/**"],
    overrides: [
      {
        files: ["components/search/HighlightedSnippet.tsx"],
        rules: ["react-doctor/no-danger"],
      },
    ],
  },
  rules: {
    "react-doctor/no-array-index-as-key": "error",
  },
  categories: {
    Maintainability: "warn",
  },
});
```

Use `doctor.config.json` for JSON config:

```json
{
  "$schema": "https://react.doctor/schema/config.json",
  "ignore": {
    "rules": ["react-doctor/no-danger"],
    "files": ["src/generated/**"],
    "overrides": [
      {
        "files": ["components/search/HighlightedSnippet.tsx"],
        "rules": ["react-doctor/no-danger"]
      }
    ]
  },
  "rules": {
    "react-doctor/no-array-index-as-key": "error"
  },
  "categories": {
    "Maintainability": "warn"
  }
}
```

## Common keys

Use these keys to control scan scope, rule severity, output surfaces, and project-specific escape hatches.

<table>
  <thead>
    <tr>
      <th>Key</th>
      <th>Type</th>
      <th>Default</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <code>ignore.rules</code>
      </td>
      <td>
        <code>string[]</code>
      </td>
      <td>
        <code>[]</code>
      </td>
      <td>Drop matching rule diagnostics after linting</td>
    </tr>
    <tr>
      <td>
        <code>ignore.files</code>
      </td>
      <td>
        <code>string[]</code>
      </td>
      <td>
        <code>[]</code>
      </td>
      <td>Exclude matching files from scanning</td>
    </tr>
    <tr>
      <td>
        <code>ignore.overrides</code>
      </td>
      <td>
        <code>{`{ files, rules? }[]`}</code>
      </td>
      <td>
        <code>[]</code>
      </td>
      <td>Drop diagnostics for matching files or rules</td>
    </tr>
    <tr>
      <td>
        <code>lint</code>
      </td>
      <td>
        <code>boolean</code>
      </td>
      <td>
        <code>true</code>
      </td>
      <td>Enable lint diagnostics</td>
    </tr>
    <tr>
      <td>
        <code>deadCode</code>
      </td>
      <td>
        <code>boolean</code>
      </td>
      <td>
        <code>true</code>
      </td>
      <td>Run dead-code analysis (skipped in diff and staged modes)</td>
    </tr>
    <tr>
      <td>
        <code>warnings</code>
      </td>
      <td>
        <code>boolean</code>
      </td>
      <td>
        <code>true</code>
      </td>
      <td>Surface warning-severity diagnostics</td>
    </tr>
    <tr>
      <td>
        <code>verbose</code>
      </td>
      <td>
        <code>boolean</code>
      </td>
      <td>
        <code>false</code>
      </td>
      <td>Show more detail</td>
    </tr>
    <tr>
      <td>
        <code>diff</code>
      </td>
      <td>
        <code>boolean | string</code>
      </td>
      <td />
      <td>Scan only changed files</td>
    </tr>
    <tr>
      <td>
        <code>blocking</code>
      </td>
      <td>
        <code>{`"error" | "warning" | "none"`}</code>
      </td>
      <td>
        <code>error</code>
      </td>
      <td>Severity that fails CI (exits non-zero)</td>
    </tr>
    <tr>
      <td>
        <code>customRulesOnly</code>
      </td>
      <td>
        <code>boolean</code>
      </td>
      <td>
        <code>false</code>
      </td>
      <td>Run only configured custom rules</td>
    </tr>
    <tr>
      <td>
        <code>share</code>
      </td>
      <td>
        <code>boolean</code>
      </td>
      <td>
        <code>true</code>
      </td>
      <td>Enable share URL behavior</td>
    </tr>
    <tr>
      <td>
        <code>noScore</code>
      </td>
      <td>
        <code>boolean</code>
      </td>
      <td>
        <code>false</code>
      </td>
      <td>Skip the score API</td>
    </tr>
    <tr>
      <td>
        <code>textComponents</code>
      </td>
      <td>
        <code>string[]</code>
      </td>
      <td>
        <code>[]</code>
      </td>
      <td>React Native text component aliases</td>
    </tr>
    <tr>
      <td>
        <code>rawTextWrapperComponents</code>
      </td>
      <td>
        <code>string[]</code>
      </td>
      <td>
        <code>[]</code>
      </td>
      <td>React Native string wrapper components</td>
    </tr>
    <tr>
      <td>
        <code>serverAuthFunctionNames</code>
      </td>
      <td>
        <code>string[]</code>
      </td>
      <td>
        <code>[]</code>
      </td>
      <td>Accepted server auth guard names</td>
    </tr>
    <tr>
      <td>
        <code>respectInlineDisables</code>
      </td>
      <td>
        <code>boolean</code>
      </td>
      <td>
        <code>true</code>
      </td>
      <td>Respect inline lint disables</td>
    </tr>
    <tr>
      <td>
        <code>adoptExistingLintConfig</code>
      </td>
      <td>
        <code>boolean</code>
      </td>
      <td>
        <code>true</code>
      </td>
      <td>Merge existing JSON lint config</td>
    </tr>
    <tr>
      <td>
        <code>rootDir</code>
      </td>
      <td>
        <code>string</code>
      </td>
      <td />
      <td>Scan a different directory, resolved relative to the config file</td>
    </tr>
    <tr>
      <td>
        <code>ignore.tags</code>
      </td>
      <td>
        <code>string[]</code>
      </td>
      <td>
        <code>[]</code>
      </td>
      <td>Disable rule families by tag before linting</td>
    </tr>
    <tr>
      <td>
        <code>rules</code>
      </td>
      <td>
        <code>object</code>
      </td>
      <td />
      <td>
        Set rule severity to <code>error</code>, <code>warn</code>, or{" "}
        <code>off</code>
      </td>
    </tr>
    <tr>
      <td>
        <code>categories</code>
      </td>
      <td>
        <code>object</code>
      </td>
      <td />
      <td>
        Set category severity to <code>error</code>, <code>warn</code>, or{" "}
        <code>off</code>
      </td>
    </tr>
    <tr>
      <td>
        <code>buckets</code>
      </td>
      <td>
        <code>object</code>
      </td>
      <td />
      <td>
        Set severity for curated buckets such as <code>compiler-cleanup</code>
      </td>
    </tr>
    <tr>
      <td>
        <code>surfaces</code>
      </td>
      <td>
        <code>object</code>
      </td>
      <td />
      <td>Control visibility on CLI, score, PR comments, and CI failure</td>
    </tr>
    <tr>
      <td>
        <code>plugins</code>
      </td>
      <td>
        <code>string[]</code>
      </td>
      <td>
        <code>[]</code>
      </td>
      <td>Load custom oxlint-shaped rule plugins</td>
    </tr>
  </tbody>
</table>

Severity overrides use `error`, `warn`, or `off`. Per-rule `rules` entries win over `categories`; `buckets` currently supports `compiler-cleanup` for React Compiler cleanup rules.

Category overrides use the five buckets: `Security`, `Bugs`, `Performance`, `Accessibility`, and `Maintainability`.

Use `rules` or `categories` when you want to change severity or stop a rule from running. Use `ignore.rules` when you want the rule to run but drop its diagnostics from the final report. Use `surfaces` when you want a diagnostic to stay visible locally but disappear from PR comments, the score, or the CI failure gate.

## React Native escape hatches

Use `textComponents` for components that behave like React Native's `<Text>`:

```json
{
  "textComponents": ["Typography", "NativeTabs.Trigger.Label"]
}
```

Use `rawTextWrapperComponents` for components that route string children through an internal text component:

```json
{
  "rawTextWrapperComponents": ["Button"]
}
```

## Server auth guards

Use `serverAuthFunctionNames` when your server actions use custom auth helpers:

```json
{
  "serverAuthFunctionNames": ["requireWorkspaceMember", "ensureSignedIn"]
}
```

## Existing lint config

React Doctor can adopt JSON ESLint and oxlint config automatically. Set `adoptExistingLintConfig: false` if you want React Doctor to ignore existing lint config.

## Narrow ignores and suppressions

Use the narrowest control that solves the problem. Ignore a rule everywhere only when it does not apply to your project:

```json
{
  "ignore": {
    "rules": ["react-doctor/no-danger"]
  }
}
```

Ignore generated files when every diagnostic in those files is noise:

```json
{
  "ignore": {
    "files": ["src/generated/**"]
  }
}
```

Ignore one rule in specific files when the exception is local:

```json
{
  "ignore": {
    "overrides": [
      {
        "files": ["components/search/HighlightedSnippet.tsx"],
        "rules": ["react-doctor/no-danger"]
      }
    ]
  }
}
```

Use an inline suppression when the exception should live with the code:

```tsx
// react-doctor-disable-next-line react-doctor/no-derived-state-effect
useEffect(() => {
  setValue(initialValue);
}, []);
```

Comma-separate rule names when one line needs more than one suppression. Use `npx react-doctor@latest why src/App.tsx:42` when a suppression does not apply.

## Custom rule plugins

React Doctor can run custom oxlint-shaped plugins from your config. Use this for team-specific conventions that do not belong in the core rule set:

```json
{
  "plugins": ["./lint/team-conventions.cjs"],
  "rules": {
    "team-conventions/no-bare-fetch": "error"
  }
}
```

Plugins export `meta` and `rules`:

```js
module.exports = {
  meta: {
    name: "team-conventions",
  },
  rules: {
    "no-bare-fetch": {
      create(context) {
        return {};
      },
    },
  },
};
```

Rule keys use `<plugin-name>/<rule-name>`. Plugin rules are opt-in and use the same CLI, score, PR comment, and CI surfaces as built-in rules.
