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:

  1. Explicit path via --config flag
  2. nestjs-doctor.config.json in the project root
  3. .nestjs-doctor.json in the project root
  4. "nestjs-doctor" key in package.json
  5. Built-in defaults

See Config loading for implementation details.

Options

KeyTypeDescription
includestring[]Glob patterns to scan (default: ["**/*.ts"])
excludestring[]Glob patterns to skip (additive with defaults)
minScorenumberMinimum passing score (0-100)
ignore.rulesstring[]Rule IDs to suppress
ignore.filesstring[]Glob patterns for files whose diagnostics are hidden
rulesRecord<string, RuleOverride | boolean>Enable/disable individual rules, and pass rule options
categoriesRecord<string, boolean>Enable/disable entire categories
customRulesDirstringPath to a directory of custom .ts rule files
telemetrybooleanSet to false to send nothing (see Telemetry)
report.telemetrybooleanSet 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
DirectiveScope
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-timestamps

See 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, so node_modules is 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.

StrategyDetected byNotes
nest-cli.json"monorepo": true plus a non-empty projects mapEach entry's root is the sub-project path, falling back to the entry name when root is absent
pnpm-workspace.yamlits packages globspnpm, and Turborepo on pnpm
package.json workspacesa workspaces array, or the Yarn { "packages": [...] } objectSkipped when pnpm-workspace.yaml exists
nx.jsonevery project.json beside itWhen the package.json check does not match, the project still counts if one of its *.module.ts files contains the text @nestjs/common
lerna.jsonits 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.