writing/blog/2026/06
BlogJun 21, 2026·6 min read

Biome 2: The All-in-One JavaScript Toolchain Guide

Replace ESLint and Prettier with Biome 2: one fast Rust toolchain for formatting and linting, now with type-aware rules that need no TypeScript compiler.

For a decade, every JavaScript project shipped with the same tax: install ESLint, install Prettier, install a dozen plugins to make them stop fighting, then wait. Two tools, two config files, two passes over your code, and a measurable chunk of every CI run spent on formatting and linting alone.

Biome collapses that stack into one binary. Written in Rust, it formats and lints JavaScript, TypeScript, JSX, JSON, CSS, and GraphQL from a single config file, in a single pass, fast enough that you stop thinking about it. With version 2 — the latest stable line is Biome 2.2 — it does something that used to require the full TypeScript compiler: type-aware linting, with no compiler dependency at all.

This guide covers what Biome 2 is, why teams are migrating, and how to adopt it without a risky big-bang rewrite.

Why one toolchain beats two

The ESLint-plus-Prettier model works, but it is held together with glue. Prettier formats; ESLint lints; eslint-config-prettier exists solely to turn off the ESLint rules that conflict with Prettier. You maintain .eslintrc, .prettierrc, two ignore files, and a mental map of which tool owns which decision.

Biome owns all of it. One biome.json controls the formatter, the linter, and the import organizer. There is no conflict layer because there are no two tools to conflict. And because it is a native binary doing the work, the speed difference is not subtle — Biome typically formats and lints a large codebase in a fraction of the time the equivalent JavaScript-based pipeline takes, which is the difference between a pre-commit hook you keep and one you quietly disable.

The payoff is concrete: fewer dependencies, one config to reason about, and a CI lint step that finishes before you have switched tabs.

Getting started

Install Biome as a dev dependency and let it write a starter config:

npm install --save-dev --save-exact @biomejs/biome
npx biome init

That creates a biome.json at your project root. A minimal but production-ready version looks like this:

{
  "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "assist": {
    "enabled": true,
    "actions": {
      "source": {
        "organizeImports": "on"
      }
    }
  }
}

Note the vcs block: with useIgnoreFile set to true, Biome respects your existing .gitignore, so you do not maintain a separate ignore list.

The three commands you actually use

Biome's CLI is small on purpose. Three commands cover daily work:

# Format files in place
npx biome format --write ./src
 
# Lint and auto-fix what is safe to fix
npx biome lint --write ./src
 
# Do everything at once: format, lint, organize imports
npx biome check --write ./src

biome check is the one to wire into pre-commit hooks and CI. It runs the formatter, the linter, and the assist actions (like import organizing) in one pass over the files. In CI, drop the --write flag so it reports problems and exits non-zero instead of editing files.

What is genuinely new in Biome 2

Version 1 already beat the old stack on speed. Version 2 closes the feature gap that kept some teams on ESLint.

Type-aware linting without the TypeScript compiler. This is the headline. Rules that need to know whether an expression is a Promise, or whether a value is an array of promises, traditionally required ESLint to run the full TypeScript type-checker — slow, and a hard dependency on typescript itself. Biome 2 ships its own type-inference engine written in Rust. A rule like noFloatingPromises can ask "what is the type of this expression?" and get an answer without ever invoking tsc. You get the safety of type-aware rules at native speed.

Multi-file analysis. Biome 2 can reason across file boundaries, which unlocks rules that depend on imports and exports rather than a single file in isolation — the foundation for catching real cross-module mistakes.

Plugins via GritQL. You can now write custom lint rules using GritQL, a structural code-search syntax, without compiling a Rust plugin or learning Biome's internals. This was the most-requested escape hatch for teams with house-specific conventions.

Domains. Rules can be grouped and toggled by domain — for example, a react or test domain — so you enable a coherent set of framework-aware rules instead of hunting through hundreds of individual toggles.

Assist actions. Beyond linting, Biome 2 has an "assist" category for source actions that are not errors, such as organizing imports or sorting object keys with useSortedKeys. These run as part of biome check.

Migrating from ESLint and Prettier

You do not have to rewrite your config by hand. Biome reads your existing setup and converts it:

# Pull in your Prettier formatting preferences
npx biome migrate prettier --write
 
# Translate your ESLint rules to Biome equivalents
npx biome migrate eslint --write

These commands merge your current Prettier options and ESLint rules into biome.json, preserving the decisions your team already made. The practical migration path is incremental: adopt Biome as the formatter first (it is the lowest-risk, highest-agreement change), confirm the diff is clean, then move linting over rule by rule. Keep ESLint installed for any rule Biome does not yet cover, and remove it once coverage is complete.

A realistic sequencing:

  1. Install Biome and run biome migrate prettier.
  2. Format the whole repo once with biome format --write and commit that as an isolated, review-friendly change.
  3. Run biome migrate eslint, then enable the linter in CI alongside (not replacing) ESLint.
  4. As Biome's rules prove out, delete the overlapping ESLint config until ESLint can be removed entirely.

Monorepo support

Biome 2 added first-class monorepo handling. You keep one root biome.json with shared defaults, and nested packages can have their own configuration that extends or overrides it. Nested configs must declare "root": false so Biome knows they are part of a larger project rather than independent roots — the biome migrate command flags any nested file that is missing this. The result is shared standards across the workspace with per-package escape hatches where they are warranted.

Editor and CI integration

Biome has an official VS Code extension (and integrations for other editors via the Language Server Protocol). Set it as your default formatter and enable format-on-save, and the editor experience matches Prettier's — instant formatting, plus inline lint diagnostics from the same engine that runs in CI. That single-source-of-truth property matters: what your editor flags is exactly what the pipeline flags, with no version drift between a Prettier plugin and a separate ESLint plugin.

For CI, the step is one line:

npx biome ci ./src

biome ci is a dedicated mode that checks formatting and linting without writing changes and is tuned for continuous integration output.

Where Biome fits

Biome is the strongest choice when you want one fast, opinionated toolchain and your stack is the web mainstream it covers: JS, TS, JSX, JSON, CSS, GraphQL. It is not yet a total replacement for every niche ESLint plugin, and Vue or Svelte single-file components are still maturing — so check coverage for your specific framework before fully cutting over. For the large majority of React, Next.js, and Node projects, though, the trade is clear: you trade two slow tools and a conflict-resolution layer for one binary that does the job in a fraction of the time.

If you have been meaning to clean up your tooling, the incremental path makes it nearly free to try. Start with the formatter on a branch, look at the diff, and let the speed make the argument.