Adopting Biome as a Unified Linter and Formatter in a TypeScript Monorepo
A concise decision guide for swapping ESLint + Prettier with Biome in a TypeScript monorepo, covering constraints, option comparison, trade‑offs, step‑by‑step implementation, and verification methods.
26 Feb 2026, 23:14 UTC

Decision and Constraints
The goal is to replace the existing ESLint + Prettier setup with Biome, a single Rust‑based tool that handles linting, formatting, and import ordering. The decision must satisfy three constraints:
- Keep CI runtime under the current budget (no more than a 20 % increase).
- Provide stable, real‑time feedback in VS Code and WebStorm via the Language Server Protocol (LSP).
- Allow a gradual migration: run Biome on changed files only during pull‑request checks while keeping legacy ESLint rules disabled at first.
Option Comparison
| Option | Pros | Cons |
|---|---|---|
| Biome all‑in‑one | Single binary, fast Rust engine, unified biome.json, built‑in formatting, linting, import sort. | Fewer niche ESLint plugins, some stylistic rule differences, migration of custom rules may be needed. |
| ESLint + Prettier (status quo) | Maximum rule ecosystem, mature plugins, fine‑grained configurability. | Two separate processes, duplicated config files, higher CI overhead, more toolchain maintenance. |
| Hybrid (ESLint for custom rules, Biome for format) | Keeps existing custom ESLint rules, gains Biome speed for formatting. | Still maintains two tools, requires careful rule delegation, potential formatting drift between formatters. |
Trade‑offs
Biome wins on execution speed and config simplicity, reducing the number of moving parts in the repository. It loses on coverage for very specific ESLint plugins (e.g., custom‑domain rules) and may require rule rewrites or suppressions. The migration cost is low for formatting and import ordering because Biome can reuse the existing prettier‑style settings, but higher for any custom lint logic that lacks a Biome equivalent.
Implementation Steps
- Add Biome as a dev dependency (run in the repository root):
npm i -D @biomejs/biome@<version>Replace
<version>with the exact release you want to pin (e.g.,1.5.0). - Generate a baseline configuration:
npx biome initThis creates a
biome.jsonwith sensible defaults. Adjust theincludesarray to match your monorepo layout, for example:{ "includes": ["packages/**/src"], "excludes": ["**/node_modules", "**/dist"] } - Enable recommended JavaScript/TypeScript linting and set formatting (edit
biome.json):{ "organizeImports": { "enabled": true }, "linter": { "enabled": true, "rules": { "recommended": true } }, "formatter": { "indentStyle": "space", "indentSize": 2 } } - Run a one‑time fix on a representative package to see the impact:
# From the repository root npx biome check --write packages//srcThis will format files, sort imports, and apply lint fixes where safe. Capture the diagnostic count before and after with
biome check --diagnosticto verify that no new errors are introduced. - Configure CI to run Biome on changed files only (example for a GitHub Actions workflow):
name: Biome CI on: [pull_request] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' - run: npm ci - name: Run Biome on changed files id: biome run: | # Get list of changed TypeScript/JavaScript files CHANGED=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} -- '*.ts' '*.tsx' '*.js' '*.jsx' | tr '\n' ' ') if [ -n "$CHANGED" ]; then npx biome check --changed $CHANGED else echo "No relevant changes" fiReplace the placeholder
${{ github.event.pull_request.base.sha }}and${{ github.sha }}with the appropriate refs for your CI system. - Validate editor integration:
- Install the Biome extension for VS Code (
biomejs.biome) or the Biome Language Server for WebStorm. - Open a file that contains a known lint issue (e.g., missing semicolon).
- Confirm that the editor underlines the issue and offers a quick‑fix via Biome LSP.
- Install the Biome extension for VS Code (
Limitations and Practical Checks
- Rule coverage – Biome may lack equivalents for certain ESLint plugins. Before fully removing ESLint, run
npx biome migrate from-eslintto see which rules cannot be mapped and decide whether to keep ESLint for those files. - Version pinning – Biome’s configuration schema can change between minor releases. Lock the exact version in
package.jsonand CI, and test upgrades in a short‑lived branch. - Editor consistency – Ensure all developers install the same Biome extension version; otherwise formatting drift can occur.
To check that the migration meets the CI budget, measure wall‑clock time for the Biome step and compare it to the previous ESLint + Prettier step on the same commit:
# Example using bash time builtin TIMEFORMAT=%R { time npx biome ci ; } 2> biome.time { time npx eslint . && npx prettier --check . ; } 2> eslint.timeThen compare the contents of
biome.timeandeslint.time. If the Biome time is within the allowed increase, the constraint is satisfied.Finally, verify that no new lint errors appear after the migration by running
npx biome cion the main branch and confirming the exit code is 0.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.