| | |

Biome 22 🧹 Monorepo Setup — Nested configs and workspace control

⏱️ Estimated read: 12 min | |

A monorepo is a repository that holds multiple packages, and each package can have its own conventions, its own rules, and its own formatting preferences. Biome’s monorepo support is built on the nested configuration model: the tool looks for the nearest biome.json from the current working directory, and if it does not find one, it walks upward until it does . The root biome.json is the baseline, and the nested biome.json in a package is the override. The mechanism is the filesystem traversal, and the traversal is the reason the tool respects the package-specific configuration without the explicit --config-path flag.

Since Biome v2, the monorepo support is native, and the nested configuration has the specific shape. The root biome.json sets the base options. The nested biome.json must have the "root": false field, or it is treated as the root configuration. The "extends": "//" microsyntax is the mechanism that inherits the root configuration from the nested file, regardless of where the nested file is. The package that does not want the root options omits the extends: "//", and the omission is the mechanism that makes the package standalone.

Key point: The monorepo setup is the root biome.json and the nested biome.json in the packages. The nested file has the "root": false field, and the "extends": "//" is the mechanism that inherits the root configuration. The package that omits the extends: "//" is standalone. The paths and the globs in the shared configuration are resolved from the configuration that extends it, not from the shared file, and the resolution is the source of the confusion.


Why the nested configuration exists

The nearest-config problem. Biome looks for the nearest biome.json from the current working directory. When the developer runs the biome check inside the packages/logger, Biome finds the packages/logger/biome.json first, and the nested file is the configuration that applies . The root biome.json is the fallback for the packages that do not have the nested file, and the fallback is the reason the root is the baseline.

The inheritance problem. The nested file can inherit the root options or not. The "extends": "//" is the microsyntax that tells Biome to extend the root configuration, and the syntax is the mechanism that the nested package follows the root’s formatting and lint rules . The package that omits the extends: "//" is standalone, and the standalone is the reason the different team can use the different conventions in the same repository.

The root problem. The "root": false field is the marker that the nested file is not the root configuration. The field is the mechanism that the Biome does not stop at the nested file when it is looking for the root, and the stopping is the reason the root configuration is still available for the inheritance . The "root": false is the requirement, and the requirement is the reason the nested file does not break the traversal.

The path-resolution problem. The paths and the globs in the configuration are resolved from the folder where the biome.json resides. When the extends is used, the paths in the shared configuration are resolved from the configuration that extends it, not from the shared file . The resolution is the mechanism that the shared configuration’s globs apply to the extending package’s files, and the resolution is the source of the confusion that the Biome discussion #7596 describes.

The workspace-control problem. The files.includes in the root can exclude the packages that should not be processed by the root run. The tip in the Biome documentation states the vendored folder or the Git submodule that uses the different Biome version should be ignored using the files.includes field . The ignore is the mechanism that the root run does not enter the nested project, and the ignore is the reason the monorepo can have the vendored project with the different Biome version.


a. The root configuration and the nested configuration

The root biome.json is the baseline. It sets the recommended rules and the formatting options, and the nested files inherit them.

// biome.json — the root configuration
{
  "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
  "linter": {
    "rules": { "recommended": true }
  },
  "formatter": {
    "lineWidth": 120,
    "indentStyle": "space"
  }
}

The root configuration is the baseline for the whole monorepo. The lineWidth: 120 and the indentStyle: "space" are the general options, and the general options are the mechanism that the nested packages inherit.

The nested configuration is the package-specific. The "extends": "//" is the inheritance, and the "root": false is the marker.

// packages/logger/biome.json — the nested configuration
{
  "extends": "//",
  "linter": {
    "rules": {
      "suspicious": { "noConsole": "off" }
    }
  }
}

The "extends": "//" is the mechanism that the logger package inherits the root options. The noConsole: "off" is the package-specific exception, and the exception is the reason the package can disable the rule that the root enables . The "root": false is omitted because the extends: "//" implies it, and the implication is the convenience.

The generated package is the different example. The formatter is disabled because the files are the code-generated.

// packages/generate/biome.json — the generated package
{
  "extends": "//",
  "formatter": { "enabled": false }
}

The formatter.enabled: false is the mechanism that the generated files are not formatted. The extends: "//" is the inheritance of the lint rules, and the inheritance is the reason the generated files are still linted . The generated package is the case where the formatting is the noise and the linting is the value.

The standalone package is the team that does not want the root options. The extends: "//" is omitted, and the package is the standalone.

// packages/analytics/biome.json — the standalone package
{
  "formatter": {
    "lineWidth": 80,
    "indentStyle": "tab"
  }
}

The lineWidth: 80 and the indentStyle: "tab" are the different conventions, and the different conventions are the reason the team uses the standalone configuration . The "root": false is the requirement, and the requirement is the reason the root run does not treat the nested file as the root.


b. The path resolution and the workspace control

The path resolution is the mechanism that the shared configuration’s globs apply to the extending package. The Biome documentation states the rule: the paths in the configuration are always resolved from the folder where the biome.json resides. When the extends is used, the paths in the shared configuration are resolved from the configuration that extends it, not from the shared file .

// common.json — the shared configuration
{
  "formatter": {
    "includes": ["src/**", "test/**"]
  }
}
// frontend/biome.json — the extending configuration
{
  "extends": ["../common.json"]
}

The frontend/biome.json extends the common.json, and the src/** and the test/** in the common are resolved from the frontend/ directory, not from the repository root . The resolution is the mechanism that the common’s globs apply to the frontend’s files, and the resolution is the reason the frontend’s formatting applies to the frontend’s src and test.

The workspace control is the files.includes in the root that ignores the nested project. The tip in the Biome documentation states the case of the vendored folder or the Git submodule that uses the different Biome version.

// biome.json — the root with the vendored ignore
{
  "files": {
    "includes": ["**", "!vendor/**", "!submodule/**"]
  }
}

The !vendor/** and the !submodule/** are the mechanisms that the root run does not enter the nested project. The ignore is the reason the vendored project with the different Biome version is not processed by the root configuration, and the ignore is the mechanism that the monorepo can have the different Biome versions in the different folders.

The scanner is the separate concern. The Biome scanner targets the folders that are relevant for the session, and the targeting is the mechanism that the large monorepo is not fully scanned on every run. The scanner respects the files.includes and the root ignore, but the .d.ts files and the package.json manifests inside the node_modules are still read when the project rules are enabled . The scanner is the mechanism that the monorepo’s performance is the acceptable.


Complete Example Session

// ============================================
// PART 1: THE ROOT CONFIGURATION
// ============================================

// biome.json
{
  "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
  "linter": { "rules": { "recommended": true } },
  "formatter": { "lineWidth": 120, "indentStyle": "space" }
}

// The root sets the baseline.

// ============================================
// PART 2: THE NESTED CONFIGURATION
// ============================================

// packages/logger/biome.json
{
  "extends": "//",
  "linter": { "rules": { "suspicious": { "noConsole": "off" } } }
}

// The logger inherits the root and disables noConsole.

// ============================================
// PART 3: THE GENERATED PACKAGE
// ============================================

// packages/generate/biome.json
{
  "extends": "//",
  "formatter": { "enabled": false }
}

// The generated files are not formatted.

// ============================================
// PART 4: THE STANDALONE PACKAGE
// ============================================

// packages/analytics/biome.json
{
  "formatter": { "lineWidth": 80, "indentStyle": "tab" }
}

// The analytics package does not inherit the root.

// ============================================
// PART 5: THE ROOT RUN
// ============================================

// biome check .

// The root run respects the nested configurations.
// The logger, the generate, and the analytics use their own.

// ============================================
// PART 6: THE PACKAGE RUN
// ============================================

// cd packages/logger && biome check .

// The package run uses the packages/logger/biome.json.

// ============================================
// PART 7: THE SHARED CONFIGURATION
// ============================================

// common.json
{
  "formatter": { "includes": ["src/**", "test/**"] }
}

// frontend/biome.json
{
  "extends": ["../common.json"]
}

// The paths are resolved from the frontend/ directory.

// ============================================
// PART 8: THE VENDORED IGNORE
// ============================================

// biome.json
{
  "files": { "includes": ["**", "!vendor/**"] }
}

// The root run does not enter the vendor folder.

// ============================================
// PART 9: THE SCANNER
// ============================================

// The scanner targets the relevant folders.
// The .d.ts and the package.json in node_modules are read
// when the project rules are enabled.

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

// [ ] The root biome.json is the baseline.
// [ ] The nested biome.json has the extends: "//".
// [ ] The standalone package omits the extends: "//".
// [ ] The nested file has the root: false.
// [ ] The paths are resolved from the extending config.
// [ ] The vendored folder is in the files.includes ignore.

The ten parts covered the root configuration, the nested configuration, the generated package, the standalone package, the root run, the package run, the shared configuration, the vendored ignore, the scanner, and the checklist. The through-line is that the nested file is the package-specific configuration, and the extends: "//" is the inheritance.


Quick Reference

The monorepo configuration

FilePurpose
The root biome.jsonThe baseline
The nested biome.jsonThe package-specific
The "root": falseThe nested marker
The "extends": "//"The root inheritance
The omitted extendsThe standalone package

The path resolution

ContextResolution
The configuration fileThe folder where the biome.json resides
The shared configurationThe configuration that extends it
The files.includesThe folder where the biome.json resides

The workspace control

MechanismPurpose
The files.includes ignoreExclude the vendored folder
The scannerTarget the relevant folders
The project domainThe cross-file analysis

Best Practices

✅ Do This:

// Use the extends: "//" for the inheritance
{ "extends": "//", "linter": { "rules": {} } }                     // ✅

// Omit the extends for the standalone package
{ "formatter": { "lineWidth": 80 } }                               // ✅

// Ignore the vendored folder in the root
{ "files": { "includes": ["**", "!vendor/**"] } }                  // ✅

❌ Don’t Do This:

// Set the root: true on the nested file
{ "root": true } // the nested file becomes the root                // ❌

// Assume the shared config paths resolve from the shared file
// The paths resolve from the extending config.                    // ❌

// Process the vendored folder with the root config
// The different Biome version conflicts.                          // ❌

Common Pitfalls

PitfallWhy It HappensFix
The nested config is not appliedThe root: false is missingAdd the field
The nested config is the rootThe root: false is missingAdd the field
The shared config paths are wrongThe paths resolve from the extending configCheck the resolution
The vendored folder is processedThe files.includes ignore is missingAdd the ignore

Real-World Examples

1. The root configuration

{ "linter": { "rules": { "recommended": true } } }

2. The nested configuration

{ "extends": "//", "linter": { "rules": { "suspicious": { "noConsole": "off" } } } }

3. The standalone package

{ "formatter": { "lineWidth": 80, "indentStyle": "tab" } }

4. The root run

biome check .

5. The package run

cd packages/logger && biome check .

6. The shared configuration

{ "extends": ["../common.json"] }

7. The vendored ignore

{ "files": { "includes": ["**", "!vendor/**"] } }

8. The generated package

{ "extends": "//", "formatter": { "enabled": false } }

9. The scanner

The scanner targets the relevant folders.

10. The root: false

{ "root": false }

Visual

The Monorepo Structure

┌──────────────────────────────────────────────────────────────┐
│  THE MONOREPO STRUCTURE                                      │
│                                                              │
│  The root biome.json                                         │
│  ┌─────────────────────┐                                    │
│  │ The baseline        │  The recommended + the format      │
│  └──────────┬──────────┘                                    │
│             │                                                │
│             ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ packages/logger     │  The extends: "//"                 │
│  │ packages/generate   │  The extends: "//"                 │
│  │ packages/analytics  │  The standalone                    │
│  └─────────────────────┘                                    │
└──────────────────────────────────────────────────────────────┘

The Inheritance

┌──────────────────────────────────────────────────────────────┐
│  THE INHERITANCE                                             │
│                                                              │
│  ✅ The extends: "//":                                        │
│  ┌─────────────────────┐                                    │
│  │ The root options    │  The nested inherits               │
│  │ + the package       │                                    │
│  └─────────────────────┘                                    │
│                                                              │
│  ❌ The omitted extends:                                      │
│  ┌─────────────────────┐                                    │
│  │ The package only    │  The standalone                    │
│  │                     │                                    │
│  └─────────────────────┘                                    │
└──────────────────────────────────────────────────────────────┘

The Path Resolution

┌──────────────────────────────────────────────────────────────┐
│  THE PATH RESOLUTION                                         │
│                                                              │
│  The common.json: { "includes": ["src/**"] }                 │
│         │                                                    │
│         ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ frontend/biome.json │  The src/** resolves from          │
│  │ extends: ["../     │  the frontend/ directory            │
│  │ common.json"]      │                                    │
│  └─────────────────────┘                                    │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
The root configThe baseline
The nested configThe package-specific
The inheritanceThe "extends": "//"
The nested markerThe "root": false
The standaloneThe omitted extends
The path resolutionFrom the extending config
The vendored ignoreThe files.includes

Key takeaways:

  • The monorepo setup is the root and the nested configurations. The root biome.json is the baseline, and the nested biome.json is the package-specific. The tool looks for the nearest config, and the traversal is the mechanism that the package-specific applies .
  • The "extends": "//" is the inheritance. The microsyntax tells Biome to extend the root configuration, and the inheritance is the mechanism that the nested package follows the root’s formatting and lint rules . The omitted extends is the standalone package.
  • The "root": false is the nested marker. The field tells Biome that the nested file is not the root, and the marker is the reason the root configuration is still available for the inheritance . The "extends": "//" implies the root: false, and the implication is the convenience.
  • The shared config paths resolve from the extending config. The paths and the globs in the shared configuration are resolved from the configuration that extends it, not from the shared file . The resolution is the source of the confusion that the Biome discussion describes.
  • The vendored folder is the files.includes ignore. The root run does not enter the vendored folder, and the ignore is the mechanism that the monorepo can have the different Biome versions in the different folders .

Remember: The Biome monorepo is the root and the nested configurations. The root is the baseline, and the nested is the package-specific. The "extends": "//" is the inheritance, and the "root": false is the marker. The shared config paths resolve from the extending config, and the vendored folder is the files.includes ignore.

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!