Biome: one tool for linting and formatting

By

Biome replaces ESLint and Prettier with one fast Rust tool. Install with biome init, run biome check, and migrate your existing config.

~~~

Biome is a formatter, a linter, and an import organizer in one native binary, with one configuration file.

For many JavaScript and TypeScript projects it can replace ESLint plus Prettier. It is written in Rust and it is fast, but speed is rarely the deciding factor. The question to answer before switching is whether Biome covers the rules and the file types your project depends on.

What each part does

The formatter rewrites code into a consistent style. The linter reports suspicious or incorrect patterns. Assist actions apply source transformations, and the one you will use most is organizing imports.

biome check runs all three. biome format and biome lint run one at a time.

Biome parses each file once and shares the result between the three. With ESLint and Prettier you have two parsers and two configurations. You usually add eslint-config-prettier to stop lint rules from conflicting with the formatter. Biome coordinates both jobs inside the same tool.

Install Biome in the project

Add Biome as an exact development dependency:

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

--save-exact pins the version, so every developer and CI run the same binary. When you want a newer one, bump it on purpose.

Check the installed version:

npx biome version

Create the configuration:

npx biome init

This writes biome.json in the project root.

Use biome.jsonc when you want comments:

npx biome init --jsonc

Commit the configuration and lockfile.

Start with a small configuration

Here is a practical biome.json for the style used in these examples:

{
  "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true
  },
  "files": {
    "includes": [
      "src/**",
      "tests/**",
      "scripts/**",
      "*.json"
    ]
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "semicolons": "asNeeded"
    }
  }
}

The schema path points at the installed package. It stays aligned with the version in node_modules without putting an exact release number in the URL.

The VCS section tells Biome to respect Git ignore files. The files.includes list defines which files Biome should inspect.

Start with the recommended rules, and add one only when it would have caught a real bug in your project.

Check the project

Run all configured checks:

npx biome check .

Without --write, Biome reports formatting differences, lint diagnostics, and import organization changes. It does not rewrite files.

On an existing project, run this first. It shows how big the diff will be before you change anything.

Apply formatting, import sorting, and safe fixes:

npx biome check --write .

Review the diff after any bulk write:

git diff

Biome separates safe and unsafe fixes. --write applies safe fixes. Add --unsafe only when you have reviewed what those rules may change:

npx biome check --write --unsafe .

I would never make --unsafe the automatic default in a pre-commit hook.

Run formatting alone

Check formatting without writing:

npx biome format src/

Format files:

npx biome format --write src/

The formatter is opinionated, like Prettier. You get a handful of options for quotes, semicolons and line width, and that’s it. The point is to stop arguing about line breaks.

Biome tries to stay close to Prettier, but the output is not byte-for-byte identical in every case. Expect a formatting diff during migration.

Run linting alone

Run the linter:

npx biome lint src/

Apply safe lint fixes:

npx biome lint --write src/

Show the documentation for a rule:

npx biome explain noUnusedVariables

Diagnostics include a category such as:

lint/correctness/noUnusedVariables

Use that category when configuring or suppressing the rule.

Configure one rule

Rules are grouped by purpose. This configuration rejects console.log calls:

{
  "linter": {
    "rules": {
      "recommended": true,
      "suspicious": {
        "noConsole": "error"
      }
    }
  }
}

Rule names and groups are part of Biome’s own configuration model. An ESLint rule with a similar purpose may have another name.

Before copying a rule from a snippet, check the current Biome rule page. It shows stability, default severity, supported languages, and whether a fix is safe.

Suppress a diagnostic narrowly

Sometimes a rule is correct for the project but wrong for one line.

Use a targeted suppression with a reason:

// biome-ignore lint/suspicious/noConsole: this CLI writes its result to stdout
console.log(report)

The comment names one rule and explains the exception.

Avoid a file-wide suppression when one line is enough. Avoid disabling a recommended group because of one false positive.

Configure different folders

Tests may need rules that production code does not. Use an override:

{
  "overrides": [
    {
      "includes": ["tests/**"],
      "linter": {
        "rules": {
          "suspicious": {
            "noConsole": "off"
          }
        }
      }
    }
  ]
}

Keep overrides few. Once several patterns overlap, it gets hard to know which rule applies to a given file.

React rules

Biome includes rules for React Hooks, including hook placement and effect dependencies.

Current Biome versions organize framework-specific rules into domains. A React project can enable the recommended React domain:

{
  "linter": {
    "domains": {
      "react": "recommended"
    }
  }
}

That covers the common rules. It does not mean every rule from every React ESLint plugin has a Biome equivalent, so compare the exact list your project uses before you remove ESLint.

Migrate from Prettier

Commit or stash your work before a migration. Then start from a clean tree so the generated diff is easy to review.

Ask Biome to read the Prettier configuration:

npx biome migrate prettier --write

Biome maps supported settings into biome.json. Its defaults differ from Prettier in areas such as tabs and semicolons, so migration is better than assuming the defaults match.

Run a formatting check next:

npx biome format .

Then format on a branch and review the complete diff:

npx biome format --write .

Biome does not format every language or framework Prettier supports. Keep Prettier for the files Biome doesn’t cover, and migrate gradually if you need to.

Migrate from ESLint

Biome can read legacy and flat ESLint configuration:

npx biome migrate eslint --write

The migration understands several popular plugin rule sets and maps supported rules into Biome names.

It cannot reproduce arbitrary JavaScript inside an ESLint config, custom rules, processors, or every plugin behavior. Current Biome documentation also notes configuration-format limits, so check the migration guide for the config used by your project.

Compare the actual enabled rules before uninstalling anything:

  1. Run the current ESLint command and save its result
  2. Run the Biome migration
  3. Run biome check without writing
  4. Review unsupported and changed rules
  5. Keep ESLint for gaps that matter
  6. Remove old dependencies only after CI passes

Using both tools temporarily is better than silently losing a security or framework rule.

Add package scripts

Give local development and CI stable commands:

{
  "scripts": {
    "check": "biome check .",
    "check:write": "biome check --write .",
    "ci": "biome ci ."
  }
}

Run the local check:

npm run check

Fix safe issues:

npm run check:write

The scripts always run the Biome version installed in the repository.

Use biome ci in automation

Biome provides a separate read-only command for CI:

npx biome ci .

It runs formatting, linting, and import organization checks without offering a write mode.

A GitHub Actions job can be small:

steps:
  - uses: actions/checkout@v6

  - uses: actions/setup-node@v6
    with:
      node-version: '24'
      cache: npm

  - run: npm ci
  - run: npm run ci

Don’t let CI rewrite and commit code. Its job is to fail when the committed source does not follow the rules, so you fix it locally.

For a large repository, Biome can check only changed files through its VCS integration. Check the complete project until you trust the rules and ignore patterns.

Check staged files

Biome has a staged mode:

npx biome check --staged

This is useful in a Git hook because it focuses on the next commit.

Use the read-only command in the hook first. Automatic writes and partially staged files need careful testing, because the working-tree file may contain changes that are not part of the commit.

The Git hooks tutorial explains how the staged snapshot differs from the working tree.

Editor integration

Install the official Biome extension in VS Code or Cursor. Set Biome as the default formatter for the languages the project assigns to it.

The editor and CLI must use compatible Biome versions and the same biome.json. Prefer the project’s installed binary when the extension supports that choice.

Enable format on save only after the first repository-wide formatting decision. Otherwise, one file may receive a large unrelated diff during normal work.

Run the CLI before committing even with editor integration. The CLI is the shared source of truth and covers files nobody opened.

Generated files and ignores

Do not lint build output, vendored code, or generated clients unless the project owns their source.

Use files.includes with ordered exceptions:

{
  "files": {
    "includes": [
      "**",
      "!dist/**",
      "!coverage/**",
      "!src/generated/**"
    ]
  }
}

Also enable vcs.useIgnoreFile so Biome respects your Git ignore files too.

Be explicit in monorepos. A root configuration should not accidentally scan caches or generated files inside every package.

Common Biome mistakes

Most problems come from treating the switch as a rename. Someone deletes ESLint before comparing rule coverage, or expects the formatter output to match Prettier byte for byte, then discovers a framework plugin rule that Biome does not have.

The other group is about configuration. You run --write --unsafe without reading the diff, or files.includes is too broad and Biome scans dist/ and node_modules/. A globally installed Biome doesn’t match the version in the lockfile, or the editor extension and CI load different configurations. Or you add many overrides before anyone understands the defaults.

None of these are hard to fix, but they are easy to miss on the first day.

When to keep ESLint or Prettier

Keep ESLint when the project relies on a custom rule, a processor, or a framework plugin Biome does not cover.

Keep Prettier for file types Biome cannot format. Different tools on non-overlapping file sets is fine. Two formatters on the same JavaScript files is not, because even small output differences mean every save produces churn.

A new project is the easiest place to adopt Biome, because there is nothing to migrate. An older repository with years of ESLint config needs the rule-by-rule comparison above.

How I would use Biome

On a new project I would run biome init, set single quotes and asNeeded semicolons, keep the recommended rules, and stop there until something real comes up.

On an existing project I would do the migration on its own branch, and in two commits: first the formatting-only change, which is large and boring, then any lint fixes that change behavior. Reviewing them together is painful.

If a rule I depend on is missing, I would keep ESLint next to Biome for that rule rather than drop it. One tool is nicer than two, but not if it stops checking something the project needs.

Tagged: DevTools · All topics

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about devtool: