cheat sheet

@biomejs/biome

Package-level reference for @biomejs/biome on npm — install, single-binary toolchain replacing prettier+eslint, Node support, and alternatives.

@biomejs/biome

What it is

@biomejs/biome (scoped — note the @biomejs/ prefix) is a single Rust-compiled toolchain that combines a formatter (Prettier-compatible output) and a linter (a curated subset of ESLint + TypeScript-ESLint rules) into one binary, configured by a single biome.json. It started as a fork of Rome (which the original Rome team archived in 2023) and is now maintained by an independent community team.

It's roughly 25–35× faster than Prettier + ESLint on the same codebase because there's no Node startup, no plugin resolution, and parsing happens once per file across both passes. It's not yet feature-parity with Prettier + ESLint — coverage sits around the 95% mark — but it's the leading "single binary replaces both" option for new JS/TS projects.

Install

bash
# Always a devDep — never ship Biome in your runtime bundle
npm install -D @biomejs/biome
pnpm add -D @biomejs/biome
yarn add -D @biomejs/biome
bun add -d @biomejs/biome

Output: biome binary on PATH under node_modules/.bin/biome.

bash
# Initialise config
npx @biomejs/biome init

Output: writes a starter biome.json at the repo root.

bash
# Day-to-day commands
npx biome format --write .          # format in place
npx biome lint .                    # lint only
npx biome check --write .           # lint + format + safe fixes in one pass
npx biome ci .                      # CI mode: error on any change, no writes

Output: Checked N files in <ms> summary; non-zero exit when issues remain.

Versioning & Node support

  • Current major line is 2.x (released early 2025 — landed multi-file project analysis, plugin model, and the unified check command). 1.x is in maintenance.
  • Requires Node 18+ to run the wrapper; the real binary is per-platform native code shipped as @biomejs/cli-<os>-<arch> optional deps.
  • Single binary, no extras to install — coverage and language support land in the core release. Plugins (in 2.x) are GritQL queries, not npm packages.
  • Always a dev dependency.
  • Strict semver — breaking rule defaults are gated behind major bumps.

Package metadata

  • Maintainer: Biome team (community / independent core team, post-Rome fork)
  • Project home: github.com/biomejs/biome
  • Docs: biomejs.dev
  • npm: npmjs.com/package/@biomejs/biome
  • License: MIT
  • First released: 2023 (as the Biome fork; predecessor Rome dates to 2020)
  • Downloads: millions per week — fastest-growing lint/format tool on npm.

Peer dependencies & extras

@biomejs/biome has no formal peer deps and no companion packages to install:

SurfaceNotes
Native binaryAuto-installed per-platform via @biomejs/cli-darwin-arm64, @biomejs/cli-linux-x64, etc. as optionalDependencies.
PluginsIn 2.x, plugins are .grit files referenced from biome.json — no npm install step.
Editor integrationbiomejs.biome for VS Code, biome.nvim for Neovim, IntelliJ plugin — install through the editor, not npm.
CI actionbiomejs/setup-biome GitHub Action — pins Biome version without touching package.json.
pre-commitDirect integration via biomejs/pre-commit hook (no Node wrapper required).

The --apply mode (legacy) was renamed --write in 2.x; --apply-unsafe became --write --unsafe. The unified check --write pass replaces the prettier+eslint+lint-staged chain.

Alternatives

ToolTrade-off
prettier + eslintThe incumbent stack — wider rule coverage, larger plugin ecosystem (Tailwind plugin, MDX plugin, every framework). Slower; two configs; coordinating versions is annoying.
dprintRust formatter (no linter), plugin-based, very fast. Pick if you only want formatting and want explicit plugin control.
oxc + oxlintRust-based JS toolchain by Boshen / VoidZero. oxlint is a much faster ESLint-compatible linter (uses .eslintrc / flat config). Pair with Prettier for formatting. Newer, narrower coverage than Biome.
romeArchived in 2023 — Biome is the successor. Don't start new projects with it.
deno fmt + deno lintBundled with Deno. Comparable to Biome in feel; tied to Deno's ecosystem and rule set.

Common gotchas

  1. Not yet at full Prettier/ESLint parity. Coverage is roughly 95%. The known gaps: some Prettier plugins (Tailwind class sorting, MDX) have no Biome equivalent (the GritQL plugin model in 2.x is closing this), and ESLint plugins for niche frameworks aren't ported. Check the rules-coverage page before migrating from a complex ESLint config.
  2. Single config format only. biome.json (or biome.jsonc) is the only config — no biome.config.js, no package.json field. Cannot share programmatic config across monorepos the way an eslint.config.js extends array can; use the extends field with a path to another JSON file instead.
  3. Editor on-save needs the right command. VS Code's "Format on Save" runs the formatter only. To get lint auto-fixes too, wire the quickfix.biome code action — otherwise you'll see lint warnings even after Save and wonder why nothing's fixing them.
  4. organizeImports is on by default. Biome reorders imports on every check --write, which churns git history on the first run. Land that change in a single dedicated commit before adopting Biome on a busy repo.
  5. Rule customisation is narrower than ESLint. Biome rules typically take only off / warn / error and a small options object — far less configurable than ESLint's per-rule schema. Some teams' ESLint configs aren't expressible in Biome.
  6. --unsafe does what it says. Unsafe fixes change runtime behaviour (e.g. removing dead code that may have side effects). Never run --write --unsafe in CI; reserve it for local one-shot sweeps and review the diff.
  7. Old --apply flag is deprecated. 1.x used --apply / --apply-unsafe. 2.x switched to --write / --write --unsafe. CI scripts copied from older blog posts silently no-op (Biome ignores unknown flags) until you upgrade.

Real-world recipes

The realistic migration paths off prettier+eslint, and the patterns Biome makes possible.

Drop-in migration from prettier+eslint

Biome ships migrate subcommands that convert existing config:

bash
# Translate .prettierrc → biome.json formatter options
npx @biomejs/biome migrate prettier

# Translate .eslintrc.* → biome.json linter rules
npx @biomejs/biome migrate eslint

# After both:
npx biome check --write .              # land the first big diff in one commit

Output:

text
✔ Wrote biome.json from .prettierrc
✔ Wrote biome.json from .eslintrc.json (47 rules mapped, 3 unsupported)
Checked 312 files in 184ms. Fixed 287 files.

The migrator handles ~80% of common configs. Rules without a Biome equivalent are flagged in the output — keep ESLint running on those files only, or accept the gap.

Land the "first big diff" cleanly

Biome's organizeImports and formatter changes can produce a 10,000-line diff on the first run. To keep history reviewable:

bash
git checkout -b chore/adopt-biome
npx biome check --write .
git add -A
git commit -m "chore: format codebase with Biome (mechanical)"

# Configure CI to fail on drift
echo '"scripts": { "format:check": "biome ci ." }' >> package.json

Output:

text
Switched to a new branch 'chore/adopt-biome'
Checked 312 files in 184ms. Fixed 287 files.
[chore/adopt-biome 7a3f1c8] chore: format codebase with Biome (mechanical)
 287 files changed, 4821 insertions(+), 4502 deletions(-)

Reviewers can skip the mechanical commit; subsequent PRs only carry the human changes.

Monorepo with shared config

Biome supports extends from a path:

json
{
  "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
  "extends": ["../../biome.base.json"],
  "files": { "include": ["src/**"] },
  "linter": { "rules": { "style": { "useNamingConvention": "error" } } }
}

Each workspace overrides what it needs without duplicating the base.

Per-language overrides

json
{
  "javascript": { "formatter": { "quoteStyle": "double", "semicolons": "always" } },
  "json": { "formatter": { "indentWidth": 2 } },
  "css": { "formatter": { "quoteStyle": "single" } }
}

Custom GritQL plugins (v2)

Biome v2's plugin model accepts .grit query files — declarative AST patterns that match and rewrite code. No npm install step; reference the file from biome.json:

json
{ "plugins": ["./tools/lint/no-console-log.grit"] }
js
// tools/lint/no-console-log.grit
language js

`console.log($args)` as $log => `// removed: console.log($args)`

Limited compared to ESLint custom rules, but covers the common "ban this AST shape" use cases.

Lint only changed files in pre-commit

For large repos where running biome check . on every commit is too slow:

yaml
# .lintstagedrc.yaml
"*.{js,jsx,ts,tsx,json,css}":
  - "biome check --write --no-errors-on-unmatched"

lint-staged passes only the staged files. Biome processes them in-place, and the formatter changes are re-staged.

Wire Biome alongside ESLint during migration

json
{
  "scripts": {
    "lint": "biome check . && eslint .",
    "lint:fix": "biome check --write . && eslint . --fix"
  }
}

Biome handles 95% of rules fast; ESLint covers the long tail. The two-tool overlap costs ~2× the runtime but lets you migrate file-by-file rather than big-bang.

Production deployment

Biome is a dev-time tool — there's no production runtime. But CI integration matters.

CI as a gate

yaml
# .github/workflows/lint.yml
- uses: biomejs/setup-biome@v2
  with: { version: latest }
- run: biome ci .

biome ci is a strict subcommand: no writes, exits non-zero on any issue, machine-readable output. Faster than biome check because it pre-allocates parallel workers based on --max-diagnostics.

Pin the version explicitly

Biome's defaults occasionally shift between minors. Pin exactly:

bash
npm install -D --save-exact @biomejs/biome

Output:

text
added 1 package, and audited 142 packages in 3s

A renovate/dependabot config can bump it on a cadence — but the pinned version means CI never breaks because someone's lockfile drifted.

Pre-commit integration

yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/biomejs/pre-commit
    rev: v2.0.0
    hooks:
      - id: biome-check
        args: [--write, --no-errors-on-unmatched]

No Node wrapper required — the pre-commit hook downloads the native binary directly.

Performance tuning

Biome is fast — but a few knobs matter on huge repos.

File discovery

biome check . walks the file tree once. On monorepos with many node_modules/, restrict the search:

bash
npx biome check src/   # explicit path beats globs

Output:

text
Checked 187 files in 92ms. No fixes needed.

biome.json files.ignore accepts gitignore-style patterns:

json
{ "files": { "ignore": ["**/node_modules/**", "**/dist/**", "**/.next/**"] } }

--max-diagnostics

Default is 20 — Biome stops collecting after 20 issues to keep output readable. Bump to see all:

bash
npx biome check --max-diagnostics=1000 .

Output:

text
Checked 312 files in 184ms.
Found 47 errors, 12 warnings across the project.

Worker threads

Biome auto-fans out across CPU cores. Override on memory-constrained CI:

bash
npx biome check --threads=2 .

Output:

text
Checked 312 files in 421ms. No fixes needed.

vs. node-tool chain

A single Rust binary doing parse-once-lint-and-format-twice beats two Node processes that parse the same files independently. Typical wins:

  • Cold cache: 25-35× faster than eslint . && prettier --check .
  • Warm: 10-15× (ESLint's cache narrows the gap)
  • CI: 2-5× (download cost amortised)

ESM/CJS interop & bundling

Biome is not a bundler — it doesn't ship runtime code, it just processes source. The npm package is a thin Node wrapper that locates the native binary; the binary itself is per-platform via optionalDependencies.

You install one logical package (@biomejs/biome); npm picks @biomejs/cli-darwin-arm64, @biomejs/cli-linux-x64, or similar. Air-gapped CI: pre-download all per-platform binaries or use the standalone curl install.

bash
# Standalone install — no Node needed
curl -fsSL https://github.com/biomejs/biome/releases/latest/download/biome-linux-x64 -o /usr/local/bin/biome
chmod +x /usr/local/bin/biome

Output: (none — exits 0 on success)

Version migration guide

From → ToHighlights
Rome → Biome 1.0Project forked from archived Rome in 2023. Existing rome.json configs can be renamed to biome.json.
1.x → 1.5First-class CSS support, useImportType rule.
1.5 → 1.9assist namespace introduced (replacing organizeImports). New JSX rules.
1 → 2The big one. Multi-file project analysis. GritQL plugin system. --apply renamed to --write; --apply-unsafe--write --unsafe. Some rule defaults tightened. assist.actions.source.organizeImports replaces the v1 top-level flag.

Common 1→2 migration friction

  • CI scripts copied from blog posts still pass --apply — Biome silently ignores unknown flags. Symptom: CI passes, files weren't actually formatted. Audit every script after upgrading.
  • organizeImports is now under assist.actions.source.organizeImports: "on". The old top-level key is removed.
  • Rule renames — a handful of rule IDs changed namespace. biome migrate from a v1 config to v2 handles most renames automatically.

Security considerations

  1. Pin the binary version. Auto-update without review means a new rule could silently downgrade severity. --save-exact + Renovate auto-PRs is the safe pattern.
  2. GritQL plugins execute against source. A malicious .grit file in a dep could rewrite code on biome check --write. Audit plugins like you'd audit ESLint plugins.
  3. --unsafe fixes change behaviour. Never wire --write --unsafe into pre-commit or CI. Reserve for one-shot sweeps with manual review.
  4. biome ci is reproducible. Same binary version + same config + same source = same output. CI failures point to either a config drift or a rule change in a new Biome version, not non-determinism.
  5. Editor integration runs the same binary. VS Code's Biome extension shells out to node_modules/.bin/biome (the wrapper picks the right per-platform binary). No separate "language server" with elevated privileges.

Configuration patterns

Minimal biome.json

json
{
  "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
  "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2 },
  "linter": { "enabled": true, "rules": { "recommended": true } },
  "assist": { "actions": { "source": { "organizeImports": "on" } } }
}

Per-glob override

json
{
  "overrides": [
    { "include": ["**/*.test.ts"], "linter": { "rules": { "style": { "useNamingConvention": "off" } } } },
    { "include": ["**/*.config.ts"], "linter": { "rules": { "suspicious": { "noExplicitAny": "off" } } } }
  ]
}

VCS integration

vcs.useIgnoreFile: true tells Biome to honour .gitignore — no need to duplicate exclude patterns:

json
{
  "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }
}

biome.jsonc for comments

Biome accepts JSON-with-comments (biome.jsonc) when you want to annotate decisions. Pick one form per repo — running both produces an "ambiguous config" warning.

Troubleshooting common errors

  • Rule name not recognised after migration — Biome uses a different rule taxonomy than ESLint. Run biome explain <rule> to confirm the name. The migration tool maps most but not all.
  • Format on Save not formatting — VS Code's "Default Formatter" is set to Prettier. Set it to "Biome" globally or per-language.
  • biome check succeeds, but files aren't fixed — missing --write. Default is read-only.
  • CI fails with "unknown option --apply" — that flag was removed in v2. Use --write.
  • Editor lints don't match CLI — editor reads biome.json from the workspace root; CLI may pick a different config if run from a subdir. Verify both find the same file.

Ecosystem integrations

Biome ships everything in one binary, so the "ecosystem" is editors, CI actions, and pre-commit hooks rather than npm plugins:

IntegrationWhat it covers
biomejs.biome (VS Code)Format-on-save, code-action quickfix, status bar
biome.nvim (Neovim)LSP wrapper
JetBrains pluginFirst-party — WebStorm / IntelliJ
biomejs/setup-biome (GitHub Action)Pin version, install binary
biomejs/pre-commitGit pre-commit hook
dprint-plugin-biomeRun Biome inside dprint for mixed-format repos

GritQL plugins are the only "extensibility" — JS-based plugins are not on the roadmap.

When NOT to use this

Biome is excellent for new projects and most app codebases. The cases that still need ESLint:

  • Niche ESLint plugins. eslint-plugin-jest, eslint-plugin-storybook, eslint-plugin-svelte (Svelte-specific rules) have no Biome equivalent yet. Coverage is closing every release; check the rule-sources matrix before migrating.
  • Custom JS rules. GritQL is declarative — patterns only, no procedural logic. Custom ESLint rules that walk the AST programmatically don't port.
  • Tailwind class sorting. prettier-plugin-tailwindcss is still the only solid option for sorting class="..." strings. Run Biome for everything else, Prettier just for *.tsx Tailwind sorting. Or accept unsorted classes.
  • Long-tail language support. Biome covers JS/TS/JSX/JSON/CSS/GraphQL/Markdown (partial). For Vue SFC, Astro, Svelte, MDX you still need Prettier plugins.
  • Existing huge ESLint configs. If migrating produces a 30% rule-coverage gap, the maintenance cost of two tools may exceed the speed win. Stay on ESLint until coverage closes.

See also