Configuration
Configuration is optional. nestjs-doctor applies reasonable defaults when no configuration file is present.
Config file
Create nestjs-doctor.config.json in your project root:
{
"minScore": 75,
"ignore": {
"rules": ["architecture/no-orm-in-services"],
"files": ["src/generated/**"]
},
"rules": {
"architecture/no-barrel-export-internals": false
},
"categories": {
"performance": false
}
}Or use a "nestjs-doctor" key in package.json:
{
"nestjs-doctor": {
"minScore": 75,
"rules": {
"architecture/no-barrel-export-internals": false
}
}
}The loader never merges the two. Whichever it finds first is used whole.
Config resolution
The config loader searches for configuration in this order:
- Explicit path via
--configflag nestjs-doctor.config.jsonin the project root.nestjs-doctor.jsonin the project root"nestjs-doctor"key inpackage.json- Built-in defaults
See Config loading for implementation details.
Options
| Key | Type | Description |
|---|---|---|
include | string[] | Glob patterns to scan (default: ["**/*.ts"]) |
exclude | string[] | Glob patterns to skip (additive with defaults) |
minScore | number | Minimum passing score (0-100) |
ignore.rules | string[] | Rule IDs to suppress |
ignore.files | string[] | Glob patterns for files whose diagnostics are hidden |
rules | Record<string, RuleOverride | boolean> | Enable/disable individual rules, and pass rule options |
categories | Record<string, boolean> | Enable/disable entire categories |
customRulesDir | string | Path to a directory of custom .ts rule files |
telemetry | boolean | Set to false to send nothing (see Telemetry) |
report.telemetry | boolean | Set to false to drop the beacon from a generated HTML report |
Set a rule to false to disable it. The object form does the same through enabled, and surfaces decides where the rule may appear (see Surfaces):
{
"rules": {
"architecture/no-barrel-export-internals": false,
"security/no-hardcoded-secret": { "enabled": false }
}
}Rule objects can also include rule-specific options. Example for no-manual-instantiation:
{
"rules": {
"architecture/no-manual-instantiation": {
"excludeClasses": ["Logger", "PinoLogger"]
}
}
}Some rules accept options under a nested options key:
{
"rules": {
"architecture/no-circular-module-deps": {
"options": { "ignoreForwardRefCycles": true }
}
}
}ignoreForwardRefCycles drops a cycle when every consecutive edge in it is wrapped in forwardRef().
Inline suppression
Suppress a rule in the source with an ignore comment. disable is an alias, accepted everywhere ignore is:
const config = eval(raw); // nestjs-doctor-ignore security/no-eval
// nestjs-doctor-ignore-next-line security/no-eval
const config = eval(raw);
// nestjs-doctor-ignore-file security/no-eval| Directive | Scope |
|---|---|
nestjs-doctor-ignore[-line] <rules> | The comment's own line |
nestjs-doctor-ignore-next-line <rules> | The line below the comment |
nestjs-doctor-ignore-file <rules> | Every line in the file |
The rule list is space- or comma-separated, and omitting it covers every rule in that scope. A -- reason trailer after the rules is ignored, so you can note why inline.
A directive counts only inside a real comment. String contents are blanked first, so one sitting in a string literal does nothing.
Suppression runs after config-based filtering, so the two compose. -line and -next-line match only code diagnostics.
Schema diagnostics (schema/*) have no line, so only nestjs-doctor-ignore-file suppresses them. Put it in the entity source for TypeORM, MikroORM, and Drizzle, or in schema.prisma for Prisma:
// nestjs-doctor-ignore-file schema/require-timestampsSee Diagnostic filtering for details.
Default excludes
These patterns are always excluded, and your exclude config is additive to them:
**/node_modules/**,**/dist/**,**/build/**,**/coverage/****/*.spec.ts,**/*.test.ts,**/*.e2e-spec.ts,**/*.e2e-test.ts**/*.d.ts**/test/**,**/tests/**,**/__tests__/****/__mocks__/**,**/__fixtures__/**,**/mock/**,**/mocks/**,**/*.mock.ts**/seeder/**,**/seeders/**,**/*.seed.ts,**/*.seeder.ts- Root-level
*.config.ts,*.config.js,*.config.mjs,*.config.cjs,*.config.mts,*.config.cts
Include and exclude
exclude: additive. Your patterns are appended to the defaults, sonode_modulesis never dropped by accident.include: replacing. Set it and only your patterns are scanned.
Monorepos
Monorepo mode is auto-detected. Five strategies are checked in order, first match wins.
| Strategy | Detected by | Notes |
|---|---|---|
nest-cli.json | "monorepo": true plus a non-empty projects map | Each entry's root is the sub-project path, falling back to the entry name when root is absent |
pnpm-workspace.yaml | its packages globs | pnpm, and Turborepo on pnpm |
package.json workspaces | a workspaces array, or the Yarn { "packages": [...] } object | Skipped when pnpm-workspace.yaml exists |
nx.json | every project.json beside it | When the package.json check does not match, the project still counts if one of its *.module.ts files contains the text @nestjs/common |
lerna.json | its packages globs, defaulting to ["packages/*"] | Skipped when useWorkspaces is true, which the package.json strategy already covers |
The nest-cli.json strategy reads the sub-project roots straight from the file:
{
"monorepo": true,
"projects": {
"api": { "root": "apps/api" },
"admin": { "root": "apps/admin" },
"shared": { "root": "libs/shared" }
}
}Three strategies resolve their globs to packages: pnpm-workspace.yaml, package.json workspaces, and lerna.json. Each keeps only the packages depending on @nestjs/core or @nestjs/common. The nx.json strategy adds the module-file fallback listed in the table.
If no package qualifies, nestjs-doctor warns and falls back to single-project mode.
Each sub-project is scanned independently. A nestjs-doctor.config.json inside a sub-project's root replaces the root config for that project. The report shows a combined score plus a per-project breakdown.
Scoring
The score is weighted by severity and category, then normalized by file count. See Scoring for the weights and the labels.