cheat sheet

Biome

Rust-based all-in-one linter and formatter for JS/TS/JSON/CSS/GraphQL. Single binary, single config, 10-100x faster than ESLint+Prettier.

Biome — One toolchain for lint, format, and import sorting

What it is

Biome is a Rust-based toolchain that combines linting, formatting, and import sorting for JavaScript, TypeScript, JSX, TSX, JSON, CSS, and GraphQL into one binary with one config file (biome.json). It is a drop-in replacement for the ESLint + Prettier pair, runs 10–100× faster on cold runs (and uses smart multi-core fan-out), and has zero plugin runtime — every rule is compiled into the binary. The trade-off versus ESLint is plugin maturity: there is no JS plugin ecosystem yet (a WASM plugin system shipped in v2 is still early). For most application repos that don't need a niche ESLint rule, Biome is a net win in speed, footprint, and ergonomics.

Install

Biome ships as a single Rust binary distributed via npm. Pin the exact version with --save-exact — patch releases occasionally tweak formatting output, and a pinned version prevents "the linter changed my files in CI" drift.

bash
# npm — recommended, pin exact version
npm install --save-dev --save-exact @biomejs/biome

# pnpm
pnpm add --save-dev --save-exact @biomejs/biome

# yarn
yarn add --dev --exact @biomejs/biome

# bun
bun add --dev --exact @biomejs/biome

# Standalone binary (no Node.js required)
curl -fsSL https://github.com/biomejs/biome/releases/latest/download/biome-linux-x64 -o biome
chmod +x biome

Output:

text
added 1 package in 1s

1 package is looking for funding
  run `npm fund` for details

Initialize a config in the repo root:

bash
npx @biomejs/biome init

Output:

text
Welcome to Biome! Let's get you started...

Files created:
  - biome.json

Next steps:
  1. Setup an editor extension: https://biomejs.dev/guides/integrate-in-editor/
  2. Try running biome on your code: npx biome check --write .

The default biome.json enables the recommended rule set, the formatter, and import sorting:

json
{
  "$schema": "https://biomejs.dev/schemas/2.1.0/schema.json",
  "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
  "files": { "ignoreUnknown": false },
  "formatter": { "enabled": true, "indentStyle": "tab" },
  "linter": {
    "enabled": true,
    "rules": { "recommended": true }
  },
  "javascript": { "formatter": { "quoteStyle": "double" } },
  "assist": { "actions": { "source": { "organizeImports": "on" } } }
}

Syntax

Biome uses a verb-first CLI: every operation is a subcommand of the biome binary. The most-used verb is check, which runs lint, format, and import sort in one pass.

bash
biome <command> [options] [paths...]

Output: (none — exits 0 on success)

Essential commands

CommandWhat it does
biome check <path>Lint + format + import sort (read-only by default)
biome check --write <path>Same, but apply all safe fixes
biome check --write --unsafe <path>Apply unsafe fixes too (review the diff)
biome format <path>Format only (no lint)
biome format --write <path>Format and overwrite files
biome lint <path>Lint only (no format)
biome lint --write <path>Lint and apply safe autofixes
biome ci <path>Like check but optimised for CI: parallel + no writes + machine-readable summary
biome initGenerate a biome.json
biome migrate eslintConvert .eslintrc.* rules to Biome
biome migrate prettierConvert .prettierrc options to Biome
biome explain <ruleName>Print the docs for a rule

check — the everyday command

biome check is the single command you'll run 95% of the time. It runs the formatter, the linter, and the import-sort assist as one parallel pipeline and is the recommended replacement for eslint . && prettier --check .. Add --write to apply fixes; in CI use biome ci instead, which is faster and prints a machine-friendly diagnostic summary.

bash
# Check the whole repo (read-only)
npx biome check .

# Check + fix safe issues
npx biome check --write .

# Check + fix safe and unsafe issues (review the diff!)
npx biome check --write --unsafe .

# Check a single directory
npx biome check src/

# Check a glob of files
npx biome check "src/**/*.{ts,tsx}"

Output (clean):

text
Checked 42 files in 38ms. No fixes applied.

Output (with issues):

text
src/app.ts:14:3 lint/suspicious/noConsoleLog ━━━━━━━━━━━━

  × Don't use console.log

    12 │ export async function main() {
    13 │   const config = loadConfig();
  > 14 │   console.log(config);
       │   ^^^^^^^^^^^^^^^^^^^
    15 │ }

  i Using console.log in production code is generally discouraged.

  i Safe fix: Remove console.log.

    12 12 │   export async function main() {
    13 13 │     const config = loadConfig();
    14    │ -   console.log(config);
    15 14 │   }


src/utils.ts format ━━━━━━━━━━━━

  i Formatter would have printed the following content:

      1   │ - export   function add(a:number, b:number){return a+b}
      1   │ + export function add(a: number, b: number) {
      2   │ +     return a + b;
      3   │ + }

Checked 42 files in 38ms. Found 2 errors.

biome.json — the single config

Biome reads exactly one config file (biome.json or biome.jsonc) from the project root. Every option for the formatter, linter, import sorter, and per-language overrides lives in this one file. There is no separate .eslintrc, .prettierrc, .editorconfig-equivalent — and Biome does not merge with parent-directory configs by default (use extends for that).

jsonc
{
  "$schema": "https://biomejs.dev/schemas/2.1.0/schema.json",

  // Use .gitignore to skip files Git ignores
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true,
    "defaultBranch": "main"
  },

  // Files Biome touches
  "files": {
    "includes": ["src/**", "test/**", "*.{js,ts,jsx,tsx,json}"],
    "ignoreUnknown": true
  },

  "formatter": {
    "enabled": true,
    "indentStyle": "space",   // "tab" | "space"
    "indentWidth": 2,
    "lineWidth": 100,
    "lineEnding": "lf"        // "lf" | "crlf" | "cr"
  },

  "javascript": {
    "formatter": {
      "quoteStyle": "double",       // "double" | "single"
      "jsxQuoteStyle": "double",
      "trailingCommas": "all",      // "all" | "es5" | "none"
      "semicolons": "always",       // "always" | "asNeeded"
      "arrowParentheses": "always", // "always" | "asNeeded"
      "bracketSpacing": true,
      "bracketSameLine": false
    }
  },

  "json": {
    "formatter": { "trailingCommas": "none" },
    "parser": { "allowComments": true, "allowTrailingCommas": true }
  },

  "css": {
    "formatter": { "quoteStyle": "double" }
  },

  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "suspicious": {
        "noConsoleLog": "warn",
        "noExplicitAny": "error"
      },
      "complexity": {
        "noForEach": "off"
      },
      "style": {
        "useImportType": "error",
        "useExportType": "error"
      }
    }
  },

  "assist": {
    "actions": {
      "source": {
        "organizeImports": "on"
      }
    }
  }
}

Use the $schema line — every modern editor will autocomplete and validate the file. The schema URL must match your installed Biome version; the biome migrate command updates it automatically when you upgrade.

Per-file overrides

Use the top-level overrides array to apply different rules or formatter options to a glob of files. The most common case is relaxing rules for tests, scripts, or generated code.

jsonc
{
  "overrides": [
    {
      "includes": ["**/*.test.ts", "**/*.spec.ts"],
      "linter": {
        "rules": {
          "suspicious": {
            "noExplicitAny": "off",
            "noConsoleLog": "off"
          }
        }
      }
    },
    {
      "includes": ["scripts/**/*.mjs"],
      "javascript": {
        "formatter": { "quoteStyle": "single" }
      }
    },
    {
      "includes": ["**/*.generated.ts"],
      "linter": { "enabled": false },
      "formatter": { "enabled": false }
    }
  ]
}

Rules and severity

Biome ships ~280 rules organised into groups: a11y, complexity, correctness, nursery (preview), performance, security, style, and suspicious. Each rule is one of three severities: "off", "warn", or "error". The "recommended": true flag turns on a curated subset (about half the catalogue) at appropriate severities.

jsonc
{
  "linter": {
    "rules": {
      // Start from the recommended set, then override
      "recommended": true,

      // Promote a warning to an error
      "suspicious": {
        "noDoubleEquals": "error"
      },

      // Demote an error to a warning
      "correctness": {
        "noUnusedVariables": "warn"
      },

      // Disable a rule completely
      "style": {
        "useTemplate": "off"
      },

      // Rule with options — use { level, options }
      "complexity": {
        "noExcessiveCognitiveComplexity": {
          "level": "warn",
          "options": { "maxAllowedComplexity": 15 }
        }
      }
    }
  }
}

List or explain rules from the CLI:

bash
# Show every rule and its current state
npx biome rage --formatter

# Print docs + examples for a single rule
npx biome explain noConsoleLog

Output:

text
# noConsoleLog
Disallow the use of `console.log`

## Examples

### Invalid

console.log("here");

### Valid

console.info("here");
console.warn("here");

For diagnostic logging, use `console.info`, `console.warn`, or `console.error`.

Suppressing diagnostics inline

Biome respects three suppression comment forms inside source files. Use them sparingly — a suppression comment without an explanation is a smell.

typescript
// Suppress the next line, single rule
// biome-ignore lint/suspicious/noExplicitAny: API returns an opaque blob
function loadConfig(): any { /* ... */ }

// Suppress the next line, all rules
// biome-ignore-all
const intentionallyBad = 1 == 1;

// Suppress a whole range
// biome-ignore-start lint/style/useImportType: legacy file
import { Foo } from "./foo";
import { Bar } from "./bar";
// biome-ignore-end lint/style/useImportType

Every biome-ignore comment requires an : explanation after the rule name. Biome will lint your suppressions and warn if they're missing or redundant — a feature ESLint and Prettier lack.

Import sorting

Biome's import sorter is an assist action (not a lint rule) — it's grouped and stable, doesn't error on misordered imports, and rewrites them on check --write. Groups (in order): bun: protocol, node: protocol, package imports, then path imports.

typescript
// Before
import { z } from "zod";
import path from "node:path";
import { Foo } from "./local";
import fs from "fs";
import { x } from "../parent";

// After `biome check --write`
import fs from "node:fs";
import path from "node:path";

import { z } from "zod";

import { x } from "../parent";

import { Foo } from "./local";

Configure it under assist:

jsonc
{
  "assist": {
    "actions": {
      "source": {
        "organizeImports": "on",
        // Group + sort by custom regex patterns
        "useSortedKeys": "on"
      }
    }
  }
}

Editor integration

The official VS Code extension is biomejs.biome. Once installed, point the editor at Biome for the relevant languages so that formatOnSave runs biome format instead of Prettier.

jsonc
// .vscode/settings.json
{
  "editor.defaultFormatter": "biomejs.biome",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "quickfix.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  },
  "[javascript]":      { "editor.defaultFormatter": "biomejs.biome" },
  "[typescript]":      { "editor.defaultFormatter": "biomejs.biome" },
  "[typescriptreact]": { "editor.defaultFormatter": "biomejs.biome" },
  "[json]":            { "editor.defaultFormatter": "biomejs.biome" },
  "[css]":             { "editor.defaultFormatter": "biomejs.biome" }
}

There are also official plugins for IntelliJ/WebStorm, Zed, Neovim (via nvim-lspconfig), and Helix.

package.json scripts

A canonical script block exposes the four most-useful commands. The lint and format scripts split work for editor-keybinding-style use; check is the workhorse you'll add to lint-staged and CI.

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

Pre-commit hook with lint-staged

bash
npm install -D husky lint-staged
npx husky init

Output: (none — exits 0 on success)

json
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx,json,css}": [
      "biome check --write --no-errors-on-unmatched"
    ]
  }
}
bash
# .husky/pre-commit
npx lint-staged

Output: (none — exits 0 on success)

The --no-errors-on-unmatched flag stops Biome from failing when lint-staged passes a glob with no matching files (e.g. a commit that only touches .md).

CI usage (GitHub Actions)

biome ci is the dedicated CI command — it's check plus parallel mode, no writes, and JSON-friendly diagnostic output via reporters.

yaml
name: CI
on: [push, pull_request]

jobs:
  biome:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: biomejs/setup-biome@v2
        with:
          version: 2.1.0
      - run: biome ci . --reporter=github

The --reporter=github flag prints diagnostics in GitHub Actions' annotation format, so errors show inline on the PR diff. Other reporters: --reporter=summary, --reporter=json, --reporter=junit, --reporter=gitlab.

Migrating from ESLint + Prettier

Biome ships built-in migration commands that read your existing .eslintrc.*, .prettierrc, .editorconfig, and package.json and write the equivalent settings into biome.json. The migration covers ~95% of typical configs; you'll need to hand-tweak any custom plugin rules that don't have a Biome equivalent.

bash
# 1. Install Biome
npm install -D --save-exact @biomejs/biome

# 2. Init Biome (creates biome.json with defaults)
npx biome init

# 3. Pull in your existing ESLint config (rules + severities)
npx biome migrate eslint --write

# 4. Pull in your existing Prettier config (formatter options)
npx biome migrate prettier --write

# 5. Run on the codebase, fixing what's safe
npx biome check --write .

# 6. Optional: review unsafe fixes
npx biome check --write --unsafe .

Output (after migrate eslint):

text
Migrating ESLint configuration to Biome
  Found .eslintrc.json
  Mapped 23 rules
  Skipped 4 rules with no Biome equivalent:
    - import/order (use Biome's import sorting in `assist.actions.source.organizeImports`)
    - jsx-a11y/click-events-have-key-events
    - react/prop-types
    - simple-import-sort/imports (use Biome's import sorting)
  Wrote biome.json

Then remove the old packages and configs from package.json:

bash
npm uninstall eslint @eslint/js typescript-eslint \
              prettier eslint-config-prettier \
              eslint-plugin-import eslint-plugin-react-hooks
rm .eslintrc.* .prettierrc* .prettierignore

Output: (none — exits 0 on success)

Comparison with ESLint + Prettier

AspectESLint + PrettierBiome
LanguageJS (Node)Rust (single binary)
Configs2-3 files (.eslintrc, .prettierrc, .prettierignore)1 file (biome.json)
Cold-run speed (10k LOC)~3-8 s~30-100 ms (10-100×)
Hot/incrementalsecondssub-100 ms
Plugin ecosystemHuge (1000+ plugins)Small (WASM plugins in v2, early)
Built-in TS supportNeeds typescript-eslint parser/pluginNative, no separate parser
Built-in JSX supportNeeds config + pluginNative
Built-in JSON / CSS / GraphQLNeeds pluginsNative
Import sortingPlugin (eslint-plugin-import)Built-in assist
Editor LSPSlow, JS-basedFast, native
Suppress-comment lintNoYes (requires explanation)
Custom rulesJS plugin APIGritQL queries (preview)

The pragmatic decision rule: if your repo doesn't rely on a niche ESLint plugin you can't live without (e.g. a domain-specific accessibility plugin or a custom monorepo path-checker), Biome is the better default for new projects in 2026. For very mature codebases with deep ESLint plugin investment, the migration cost can outweigh the speed win.

Common pitfalls

  1. Forgetting --save-exact — formatter output can change between minor versions, leading to CI churn. Always pin Biome with --save-exact and bump deliberately.
  2. Mixing Biome with Prettier formatting on the same files — pick one. If you keep Prettier for .md files only, scope each tool with includes to non-overlapping globs.
  3. biome check modifies files — it doesn't, unless you pass --write. The default is read-only; running it in CI without --write is correct.
  4. --unsafe fixes break code — that's why they're unsafe. Always review the diff before committing. Common offenders: rewriting == to === when the loose equality was intentional.
  5. No support for .eslintrc overrides at runtime — Biome reads biome.json only. Migrate once with biome migrate eslint; don't try to keep both configs in sync.
  6. VS Code formatter mismatch — installing the Biome extension does not change the default formatter. You must explicitly set editor.defaultFormatter per language (see snippet above) or saving will still run Prettier.
  7. Ignoring node_modules slowly — by default Biome respects .gitignore only if vcs.useIgnoreFile: true is set. Without that, it walks every file. Turn the VCS option on for large repos.
  8. CI not seeing errors inline — pass --reporter=github (GitHub Actions) or --reporter=gitlab (GitLab CI) so annotations show on the PR/MR diff instead of buried in logs.

Real-world recipes

Migrating a Next.js app from ESLint + Prettier to Biome

A typical Next.js repo with eslint-config-next + Prettier + eslint-plugin-import migrates in five steps.

bash
# 1. Install Biome (pinned)
npm install -D --save-exact @biomejs/biome

# 2. Init + import old configs
npx biome init
npx biome migrate eslint --write
npx biome migrate prettier --write

# 3. Apply formatting + import-sort to the whole tree
npx biome check --write .

# 4. Replace the old npm scripts
#    "lint": "next lint"          ->  "lint": "biome check ."
#    "format": "prettier --write" ->  "format": "biome format --write ."
#    "check": "biome check ."

# 5. Remove dead deps + configs
npm uninstall eslint eslint-config-next prettier \
              eslint-config-prettier eslint-plugin-import
rm .eslintrc.json .prettierrc .prettierignore

Output:

text
Migrating ESLint configuration to Biome
  Found .eslintrc.json (extends: next, prettier)
  Mapped 18 rules
  Skipped 6 rules with no direct equivalent (most have Biome equivalents via different rule names — review biome.json)
Checked 412 files in 287ms. Applied 31 fixes.

Pre-commit + CI matrix

A repo that wants Biome to gate both commits and PRs.

bash
# Local: lint-staged + husky
npm install -D husky lint-staged
npx husky init

Output: (none — exits 0 on success)

json
{
  "scripts": { "prepare": "husky" },
  "lint-staged": {
    "*.{js,jsx,ts,tsx,json,css}": [
      "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"
    ]
  }
}
yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: biomejs/setup-biome@v2
        with: { version: 2.1.0 }
      - run: biome ci . --reporter=github

  build:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci
      - run: npm run build

Differential lint — only changed files in a PR

For monorepos where checking the whole tree on every commit is overkill, lint only files changed against main.

bash
# Get the changed file list
CHANGED=$(git diff --name-only --diff-filter=ACMR origin/main...HEAD | grep -E '\.(js|jsx|ts|tsx|json|css)$')

# Hand them to Biome (xargs splits long lists across multiple invocations)
echo "$CHANGED" | xargs -r npx biome check --no-errors-on-unmatched

Output:

text
Checked 8 files in 21ms. No fixes applied.

Multi-package monorepo — one config, package-local overrides

Put a single biome.json at the repo root and use overrides.includes to scope per-package rules.

jsonc
// biome.json (repo root)
{
  "$schema": "https://biomejs.dev/schemas/2.1.0/schema.json",
  "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
  "linter": { "rules": { "recommended": true } },
  "overrides": [
    {
      "includes": ["packages/api/**"],
      "javascript": { "formatter": { "quoteStyle": "single" } },
      "linter": {
        "rules": { "suspicious": { "noConsoleLog": "error" } }
      }
    },
    {
      "includes": ["packages/web/**"],
      "linter": {
        "rules": { "a11y": { "recommended": true } }
      }
    },
    {
      "includes": ["packages/*/test/**"],
      "linter": {
        "rules": { "suspicious": { "noExplicitAny": "off" } }
      }
    }
  ]
}

Then run from the root:

bash
npx biome check packages/

Output:

text
Checked 1284 files in 412ms. No fixes applied.

Editor save-on-write with safe-only fixes

A repo that wants format-on-save plus safe lint fixes (e.g. useImportType) but never unsafe ones.

jsonc
// .vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "biomejs.biome",
  "editor.codeActionsOnSave": {
    "source.fixAll.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  },
  "biome.lspBin": "./node_modules/@biomejs/biome/bin/biome"
}

The biome.lspBin line forces VS Code to use the project's pinned Biome binary instead of the extension's bundled one — important for reproducibility on shared teams.