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.
# 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:
added 1 package in 1s
1 package is looking for funding
run `npm fund` for details
Initialize a config in the repo root:
npx @biomejs/biome init
Output:
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:
{
"$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.
biome <command> [options] [paths...]
Output: (none — exits 0 on success)
Essential commands
| Command | What 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 init | Generate a biome.json |
biome migrate eslint | Convert .eslintrc.* rules to Biome |
biome migrate prettier | Convert .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.
# 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):
Checked 42 files in 38ms. No fixes applied.
Output (with issues):
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).
{
"$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
$schemaline — every modern editor will autocomplete and validate the file. The schema URL must match your installed Biome version; thebiome migratecommand 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.
{
"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.
{
"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:
# Show every rule and its current state
npx biome rage --formatter
# Print docs + examples for a single rule
npx biome explain noConsoleLog
Output:
# 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.
// 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-ignorecomment requires an: explanationafter 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.
// 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:
{
"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.
// .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.
{
"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
npm install -D husky lint-staged
npx husky init
Output: (none — exits 0 on success)
{
"lint-staged": {
"*.{js,jsx,ts,tsx,json,css}": [
"biome check --write --no-errors-on-unmatched"
]
}
}
# .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.
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.
# 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):
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:
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
| Aspect | ESLint + Prettier | Biome |
|---|---|---|
| Language | JS (Node) | Rust (single binary) |
| Configs | 2-3 files (.eslintrc, .prettierrc, .prettierignore) | 1 file (biome.json) |
| Cold-run speed (10k LOC) | ~3-8 s | ~30-100 ms (10-100×) |
| Hot/incremental | seconds | sub-100 ms |
| Plugin ecosystem | Huge (1000+ plugins) | Small (WASM plugins in v2, early) |
| Built-in TS support | Needs typescript-eslint parser/plugin | Native, no separate parser |
| Built-in JSX support | Needs config + plugin | Native |
| Built-in JSON / CSS / GraphQL | Needs plugins | Native |
| Import sorting | Plugin (eslint-plugin-import) | Built-in assist |
| Editor LSP | Slow, JS-based | Fast, native |
| Suppress-comment lint | No | Yes (requires explanation) |
| Custom rules | JS plugin API | GritQL 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
- Forgetting
--save-exact— formatter output can change between minor versions, leading to CI churn. Always pin Biome with--save-exactand bump deliberately. - Mixing Biome with Prettier formatting on the same files — pick one. If you keep Prettier for
.mdfiles only, scope each tool withincludesto non-overlapping globs. biome checkmodifies files — it doesn't, unless you pass--write. The default is read-only; running it in CI without--writeis correct.--unsafefixes break code — that's why they're unsafe. Always review the diff before committing. Common offenders: rewriting==to===when the loose equality was intentional.- No support for
.eslintrcoverrides at runtime — Biome readsbiome.jsononly. Migrate once withbiome migrate eslint; don't try to keep both configs in sync. - VS Code formatter mismatch — installing the Biome extension does not change the default formatter. You must explicitly set
editor.defaultFormatterper language (see snippet above) or saving will still run Prettier. - Ignoring
node_modulesslowly — by default Biome respects.gitignoreonly ifvcs.useIgnoreFile: trueis set. Without that, it walks every file. Turn the VCS option on for large repos. - 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.
# 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:
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.
# Local: lint-staged + husky
npm install -D husky lint-staged
npx husky init
Output: (none — exits 0 on success)
{
"scripts": { "prepare": "husky" },
"lint-staged": {
"*.{js,jsx,ts,tsx,json,css}": [
"biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"
]
}
}
# .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.
# 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:
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.
// 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:
npx biome check packages/
Output:
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.
// .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.