Biome's Unified Config: How .biome.json Handles Project Roots and File Patterns
Biome replaces ESLint+Prettier with one .biome.json. This post shows how project root detection and unified file patterns work, with a monorepo config example and verification steps.
20 Aug 2026, 13:34 UTC

The Problem: Fragmented Configs and Leaky Boundaries
Teams migrating from ESLint and Prettier often juggle .eslintrc, .prettierrc, and editor-specific settings. Each tool resolves files differently, so a glob that works for formatting might miss linting, or a monorepo root config leaks into a nested package. Biome replaces that stack with a single .biome.json that drives formatting, linting, and import organization. The catch: you need to understand how Biome discovers the project root and how its files.include / files.ignore patterns apply across every sub‑command.
How Biome Finds Your Project Root
Biome walks upward from the current working directory looking for .biome.json or package.json. The first match becomes the project root. In a monorepo this can cause cross‑package leakage if a nested package lacks its own config. Two top‑level keys let you control the boundary:
projectRoot(string, relative to the config file) forces the root to a specific directory.vcs.enabled(boolean, default true) tells Biome to stop at the nearest VCS root (usually.git).
If you have a repo layout like packages/app and packages/lib each with their own .biome.json, set "vcs": { "enabled": false } in the root config so Biome doesn't treat the whole repo as one project.
File Inclusion and Exclusion: One Pattern Set for All Tools
The files object accepts include and ignore arrays of glob patterns. These patterns are evaluated relative to the project root and apply to biome format, biome lint, biome check, and the organize‑imports action. There is no per‑tool override, which eliminates the classic mismatch where Prettier formats *.md but ESLint ignores them.
Patterns follow the glob crate semantics: ** matches any depth, * matches within a segment, and ! negates a pattern. Order matters—later entries can re‑include something an earlier ignore excluded.
Worked Example: Configuring a Monorepo with Nested Packages
Assume a repository:
repo/
.biome.json # root config
package.json
packages/
app/
.biome.json # package‑specific config
src/
lib/
src/
Root .biome.json (repo/.biome.json):
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": { "enabled": false },
"files": {
"include": ["**"],
"ignore": ["**/node_modules/**", "**/dist/**", "packages/*/src/generated/**"]
},
"formatter": { "enabled": true },
"linter": { "enabled": true },
"javascript": { "formatter": { "semicolons": "always" } }
}
Package config (repo/packages/app/.biome.json):
{
"extends": ["../../.biome.json"],
"projectRoot": ".",
"files": {
"include": ["src/**/*.ts", "src/**/*.tsx"],
"ignore": ["src/generated/**"]
},
"organizeImports": { "enabled": true }
}
Run from repo/packages/app:
# Dry‑run to see which files Biome will touch
biome check --files-ignore-unknown=true .
# Apply formatting, lint fixes, and organize imports
biome check --write .
Run from the repo root to verify the root config is not leaking into packages/lib (which has no config):
biome check --files-ignore-unknown=true packages/lib
Expected check: Biome should report "No configuration file found" for packages/lib because vcs.enabled: false stops at the package root and projectRoot: "." in the app config confines it.
Trade‑offs: Rule Parity and Organize Imports Behavior
Biome's lint rule set covers many core JavaScript/TypeScript patterns, but custom ESLint plugins (e.g., eslint-plugin-react-hooks, eslint-plugin-testing-library) have no direct equivalent. Teams with heavy plugin reliance often keep a minimal ESLint run for those rules while using Biome for everything else.
Organize imports is a source‑modifying action. Enable it in CI (biome check --write) and review diffs before turning on editor "format on save". A misconfigured files.ignore can cause imports to be reorganized in generated files, creating noisy diffs.
Configuration keys change across major versions. For example, files.ignore semantics shifted in v1.8. Always pin the schema version in $schema and test after upgrades.
Actionable Closing: Verify Before You Commit
- Create a throwaway branch and add the example configs above.
- Run
biome check --writeon a sample file set; compare the output with your current Prettier/ESLint run usinggit diff. - Inspect
biome --helpandbiome config --helpto confirm the top‑level keys available in your installed version. - Test project root detection:
cd packages/app && biome configshould print the merged config;cd ../lib && biome configshould error.
If the diffs look clean and the root detection behaves as expected, you have a reliable single source of truth for formatting, linting, and import organization.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.