| | |

Biome 21 🧹 Overrides — Per-file and per-directory configuration

⏱️ Estimated read: 12 min | |

The overrides array is the mechanism that applies a different configuration to a subset of the files without splitting the project into the multiple biome.json files. The array is the list of the objects, and each object has the includes globs and the configuration that applies to the matched files. The configuration in the override merges with the top-level configuration, and the merge is the mechanism that lets the override change only the settings it cares about . The overrides is the per-file and the per-directory configuration, and the per-directory is the special case where the glob matches the folder.

The override’s includes is the same glob syntax as the top-level files.includes, and the order of the overrides matters. The overrides are applied in the array order, and the later override wins when the multiple overrides match the same file . The order is the mechanism that lets the specific override be the last, and the last is the reason the specific override wins. The overrides is the Biome 2.x feature that replaced the ESLint’s overrides array, and the migration command is the mechanism that ports the ESLint overrides .

Key point: The overrides array applies the different configuration to the matched files. The includes globs are the same as the top-level files.includes, and the order matters—the later override wins. The override merges with the top-level configuration, and the merge is the mechanism that the override changes only the settings it declares.


Why the overrides exist

The per-directory problem. A monorepo can have the different packages with the different conventions. The apps/web is the React project, and the packages/cli is the Node.js project. The two need the different linter settings—the React domain in the web, the Node.js globals in the CLI. The overrides is the mechanism that applies the different configuration to the different directories, and the different configuration is the reason the single biome.json works for the monorepo .

The per-file-type problem. A project can have the .test.ts files that need the test globals, and the .ts files that do not. The overrides with the includes: ["**/*.test.ts"] is the mechanism that applies the test configuration to the test files, and the test configuration is the reason the describe and the it are not flagged in the test files .

The generated-file problem. A project can have the generated/** directory that the code generator produces, and the generated files should be excluded from the lint and the format. The overrides with the formatter: { enabled: false } and the linter: { enabled: false } is the mechanism that disables the tools for the generated directory, and the disable is the reason the generated files are not churned.

The specific-rule problem. A project can have the specific file that needs the specific rule disabled. The overrides with the linter.rules is the mechanism that disables the rule for the specific file, and the disable is the reason the file-specific exception does not need the inline suppression . The override is the configuration-level exception, and the inline suppression is the code-level exception. The choice is the scope, and the scope is the number of the files.

The order problem. The multiple overrides can match the same file, and the order is the mechanism that decides which one wins. The later override is the winner, and the winner is the reason the specific override is the last in the array . The order is the mechanism that the developer must be aware of, and the awareness is the reason the overrides are the array rather than the object.


a. The per-file and the per-directory override

The per-file override is the includes glob that matches the specific file. The override configuration is the subset that the file needs.

// biome.json — the per-file override
{
  "linter": {
    "rules": { "recommended": true }
  },
  "overrides": [
    {
      // The test files need the test globals.
      "includes": ["**/*.test.ts", "**/*.spec.ts"],
      "linter": {
        "domains": { "test": "recommended" }
      }
    }
  ]
}

The includes: ["**/*.test.ts", "**/*.spec.ts"] is the mechanism that matches the test files. The linter.domains.test is the mechanism that enables the test domain for the matched files . The override merges with the top-level recommended: true, and the merge is the reason the test files have both the recommended rules and the test domain.

The per-directory override is the includes glob that matches the directory. The apps/web/** is the mechanism that matches the web app, and the override configuration is the web-specific.

// biome.json — the per-directory override
{
  "overrides": [
    {
      // The web app has the React settings.
      "includes": ["apps/web/**"],
      "linter": {
        "domains": { "react": "recommended" }
      },
      "javascript": {
        "formatter": { "quoteStyle": "single" }
      }
    },
    {
      // The CLI has the Node.js settings.
      "includes": ["packages/cli/**"],
      "linter": {
        "domains": { "project": "recommended" }
      }
    }
  ]
}

The apps/web/** and the packages/cli/** are the mechanisms that match the directories. The React domain and the Node.js settings are the per-directory configurations, and the configurations are the reason the monorepo works with the single biome.json . The merge is the reason the top-level rules apply to the both.


b. The override order and the specificity

The overrides are applied in the array order, and the later override wins when the multiple overrides match the same file. The order is the mechanism that the specific override is the last.

// biome.json — the override order
{
  "overrides": [
    {
      // The general override for all the test files.
      "includes": ["**/*.test.ts"],
      "linter": {
        "rules": { "suspicious": { "noExplicitAny": "off" } }
      }
    },
    {
      // The specific override for the e2e test files.
      // The later override wins for the e2e files.
      "includes": ["e2e/**/*.test.ts"],
      "linter": {
        "rules": { "suspicious": { "noExplicitAny": "error" } }
      }
    }
  ]
}

The **/*.test.ts is the general override, and the e2e/**/*.test.ts is the specific override. The e2e/**/*.test.ts matches the e2e/foo.test.ts, and the later override is the winner, so the noExplicitAny is the error for the e2e test files . The order is the mechanism that the specific override wins, and the win is the reason the order matters.

The overrides order is the mechanism that the developer must be aware of. The general override first and the specific override last is the convention, and the convention is the reason the specific override is the last . The reverse order would let the general override win, and the general override is the reason the specific is overridden.

The includes in the override is the same glob syntax as the top-level files.includes. The * does not cross the /, the ** is the recursive, and the ! is the negative. The negative includes in the override is the mechanism that excludes the files from the override .

// biome.json — the negative includes
{
  "overrides": [
    {
      // The override applies to the src files.
      // The negative excludes the legacy directory.
      "includes": ["src/**", "!src/legacy/**"],
      "linter": {
        "rules": { "style": { "useConst": "error" } }
      }
    }
  ]
}

The !src/legacy/** is the mechanism that excludes the legacy directory from the override. The negative is the reason the legacy files are not subject to the override’s rules, and the exclusion is the mechanism that the per-directory override is the selective.


Complete Example Session

// ============================================
// PART 1: THE TEST FILE OVERRIDE
// ============================================

{
  "linter": {
    "rules": { "recommended": true }
  },
  "overrides": [
    {
      "includes": ["**/*.test.ts", "**/*.spec.ts"],
      "linter": {
        "domains": { "test": "recommended" }
      }
    }
  ]
}

// The test files get the test domain.

// ============================================
// PART 2: THE PER-DIRECTORY OVERRIDE
// ============================================

{
  "overrides": [
    {
      "includes": ["apps/web/**"],
      "linter": {
        "domains": { "react": "recommended" }
      }
    }
  ]
}

// The web app gets the React domain.

// ============================================
// PART 3: THE GENERATED FILES OVERRIDE
// ============================================

{
  "overrides": [
    {
      "includes": ["generated/**"],
      "formatter": { "enabled": false },
      "linter": { "enabled": false }
    }
  ]
}

// The generated files are not formatted or linted.

// ============================================
// PART 4: THE SPECIFIC FILE OVERRIDE
// ============================================

{
  "overrides": [
    {
      "includes": ["src/legacy.js"],
      "linter": {
        "rules": { "suspicious": { "noDoubleEquals": "off" } }
      }
    }
  ]
}

// The legacy.js has the noDoubleEquals disabled.

// ============================================
// PART 5: THE OVERRIDE ORDER
// ============================================

{
  "overrides": [
    {
      "includes": ["**/*.test.ts"],
      "linter": { "rules": { "suspicious": { "noExplicitAny": "off" } } }
    },
    {
      "includes": ["e2e/**/*.test.ts"],
      "linter": { "rules": { "suspicious": { "noExplicitAny": "error" } } }
    }
  ]
}

// The e2e test files get the error because the later override wins.

// ============================================
// PART 6: THE NEGATIVE INCLUDES
// ============================================

{
  "overrides": [
    {
      "includes": ["src/**", "!src/legacy/**"],
      "linter": { "rules": { "style": { "useConst": "error" } } }
    }
  ]
}

// The src files get the useConst, but the legacy files do not.

// ============================================
// PART 7: THE FORMATTER OVERRIDE
// ============================================

{
  "overrides": [
    {
      "includes": ["**/*.md"],
      "formatter": { "lineWidth": 120 }
    }
  ]
}

// The markdown files get the 120 line width.

// ============================================
// PART 8: THE DOMAIN OVERRIDE
// ============================================

{
  "overrides": [
    {
      "includes": ["tests/**"],
      "linter": { "domains": { "test": "all" } }
    }
  ]
}

// The test directory gets all the test rules.

// ============================================
// PART 9: THE RULE OPTIONS OVERRIDE
// ============================================

{
  "overrides": [
    {
      "includes": ["src/**"],
      "linter": {
        "rules": {
          "style": {
            "useNamingConvention": {
              "level": "error",
              "options": { "strictCase": false }
            }
          }
        }
      }
    }
  ]
}

// The src files get the naming convention with the options.

// ============================================
// PART 10: THE CHECKLIST
// ============================================

// [ ] The override has the includes glob.
// [ ] The override configuration is the subset.
// [ ] The override merges with the top-level.
// [ ] The specific override is the last.
// [ ] The negative includes exclude the files.
// [ ] The per-directory override matches the directory.

The ten parts covered the test file override, the per-directory override, the generated files override, the specific file override, the override order, the negative includes, the formatter override, the domain override, the rule options override, and the checklist. The through-line is that the override is the merge, and the order is the specificity.


Quick Reference

The override structure

FieldPurpose
includesThe glob patterns that select the files
The tool sectionsThe configuration that applies to the matched files

The order rule

OrderResult
The general override firstThe later specific override wins
The specific override firstThe later general override wins

The includes globs

PatternMatches
**/*.test.tsThe test files at any depth
apps/web/**The web app directory
!src/legacy/**The exclusion

The common overrides

Use caseConfiguration
The test filesThe test domain
The generated filesThe formatter and the linter disabled
The legacy fileThe specific rule disabled
The per-directoryThe domain and the language settings

Best Practices

✅ Do This:

// Use the per-file override for the test files
{ "includes": ["**/*.test.ts"], "linter": { "domains": { "test": "recommended" } } } // ✅

// Put the specific override last
[{ "includes": ["**/*.test.ts"] }, { "includes": ["e2e/**/*.test.ts"] }] // ✅

// Use the negative includes for the exclusion
{ "includes": ["src/**", "!src/legacy/**"] }                       // ✅

❌ Don’t Do This:

// Put the specific override first
[{ "includes": ["e2e/**/*.test.ts"] }, { "includes": ["**/*.test.ts"] }] // ❌

// Disable the formatter for the whole project
{ "formatter": { "enabled": false } } // the top-level              // ❌

// Use the override for the single-line exception
// The inline suppression is the better choice.                    // ❌

Common Pitfalls

PitfallWhy It HappensFix
The override does not applyThe includes glob does not matchCheck the glob against the file path
The specific override is ignoredThe order is wrongPut the specific override last
The generated files are formattedThe override is missingAdd the override with the disabled tools
The legacy file is includedThe negative includes are missingAdd the !src/legacy/**

Real-World Examples

1. The test file override

{ "includes": ["**/*.test.ts"], "linter": { "domains": { "test": "recommended" } } }

2. The per-directory override

{ "includes": ["apps/web/**"], "linter": { "domains": { "react": "recommended" } } }

3. The generated files override

{ "includes": ["generated/**"], "formatter": { "enabled": false } }

4. The specific file override

{ "includes": ["src/legacy.js"], "linter": { "rules": { "suspicious": { "noDoubleEquals": "off" } } } }

5. The override order

[{ "includes": ["**/*.test.ts"] }, { "includes": ["e2e/**/*.test.ts"] }]

6. The negative includes

{ "includes": ["src/**", "!src/legacy/**"] }

7. The formatter override

{ "includes": ["**/*.md"], "formatter": { "lineWidth": 120 } }

8. The domain override

{ "includes": ["tests/**"], "linter": { "domains": { "test": "all" } } }

9. The rule options override

{ "includes": ["src/**"], "linter": { "rules": { "style": { "useNamingConvention": { "level": "error", "options": { "strictCase": false } } } } } }

10. The disabled tools

{ "includes": ["vendor/**"], "formatter": { "enabled": false }, "linter": { "enabled": false } }

Visual

The Override Merge

┌──────────────────────────────────────────────────────────────┐
│  THE OVERRIDE MERGE                                          │
│                                                              │
│  The top-level configuration                                 │
│  ┌─────────────────────┐                                    │
│  │ recommended: true   │  The baseline                      │
│  └──────────┬──────────┘                                    │
│             │                                                │
│             ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ The override        │  The subset                        │
│  │ domains.test        │  The merge                         │
│  └──────────┬──────────┘                                    │
│             │                                                │
│             ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ The matched files   │  The result                        │
│  │ get both            │                                    │
│  └─────────────────────┘                                    │
└──────────────────────────────────────────────────────────────┘

The Override Order

┌──────────────────────────────────────────────────────────────┐
│  THE OVERRIDE ORDER                                          │
│                                                              │
│  The overrides array                                         │
│  ┌─────────────────────┐                                    │
│  │ 1. **/*.test.ts     │  The general                       │
│  │ 2. e2e/**/*.test.ts │  The specific                      │
│  └──────────┬──────────┘                                    │
│             │                                                │
│             ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ The later wins      │  The e2e files get the specific    │
│  └─────────────────────┘                                    │
└──────────────────────────────────────────────────────────────┘

The Negative Includes

┌──────────────────────────────────────────────────────────────┐
│  THE NEGATIVE INCLUDES                                       │
│                                                              │
│  The includes: ["src/**", "!src/legacy/**"]                  │
│         │                                                    │
│         ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ The src files       │  The override applies              │
│  └──────────┬──────────┘                                    │
│             │                                                │
│             ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ The legacy files    │  The override does not apply       │
│  └─────────────────────┘                                    │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
The arrayoverrides
The selectorincludes glob
The mergeThe override merges with the top-level
The orderThe later override wins
The negativeThe ! prefix
The per-directoryThe apps/web/** glob
The disabled toolsThe formatter.enabled: false and the linter.enabled: false

Key takeaways:

  • The overrides array applies the different configuration to the matched files. The includes globs select the files, and the override configuration is the subset that the files need. The override merges with the top-level configuration, and the merge is the reason the override changes only the settings it declares .
  • The order matters. The overrides are applied in the array order, and the later override wins when the multiple overrides match the same file. The specific override should be the last, and the last is the reason the specific override wins .
  • The negative includes excludes the files from the override. The !src/legacy/** is the mechanism that the legacy directory is not subject to the override’s rules, and the exclusion is the reason the per-directory override is the selective .
  • The per-directory override is the monorepo mechanism. The apps/web/** and the packages/cli/** are the mechanisms that apply the React domain and the Node.js settings to the different directories, and the different settings are the reason the single biome.json works for the monorepo .
  • The generated files override disables the tools. The formatter.enabled: false and the linter.enabled: false are the mechanisms that the generated files are not formatted or linted, and the disable is the reason the generated files are not churned.

Remember: The Biome overrides are the per-file and the per-directory configuration. The includes globs select the files, and the override configuration is the subset. The override merges with the top-level, and the order is the specificity. The negative includes exclude the files, and the disabled tools are the generated-file mechanism. The override is the configuration-level exception, and the inline suppression is the code-level exception.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!