Biome: The Fast Formatter and Linter for JavaScript and TypeScript
Biome: The Fast Formatter and Linter for JavaScript and TypeScript
The JavaScript ecosystem has relied on two separate tools for code quality for over a decade: Prettier for formatting and ESLint for linting. This works, but it comes with real costs. Two separate configurations. Two sets of plugins. Conflicting rules that require yet another package (eslint-config-prettier) to resolve. Slow execution because both tools parse your entire codebase independently. A node_modules directory bloated with dozens of transitive dependencies just for your dev tooling.
Biome replaces both tools with a single, fast binary. Written in Rust, it formats and lints JavaScript, TypeScript, JSX, TSX, JSON, and CSS. It is 20-100x faster than Prettier and ESLint combined. It has zero dependencies. And it achieves over 97% compatibility with Prettier's formatting output, so switching is practical rather than theoretical.
Why Biome Over Prettier + ESLint?
Speed: Biome formats a large codebase in milliseconds where Prettier takes seconds. For linting, the difference is even more dramatic because Biome parses the code once for both operations.
Single tool: One configuration file, one CLI, one set of rules. No compatibility layer needed between formatter and linter.
Zero dependencies: Biome is a single binary. Your node_modules shrinks because you drop Prettier, ESLint, and their combined dozens of plugins and configs.
Better defaults: Biome ships with sensible defaults that work without any configuration file. You can run biome check . on a fresh project and get useful results immediately.
Consistent behavior: Since formatting and linting share the same parser, there are no edge cases where the formatter produces code that the linter flags, or vice versa.
Installation
npm/Bun/pnpm (Recommended)
# npm
npm install --save-dev --save-exact @biomejs/biome
# Bun
bun add --dev --exact @biomejs/biome
# pnpm
pnpm add --save-dev --save-exact @biomejs/biome
The --save-exact flag is recommended because Biome follows a rapid release cycle and you want reproducible builds.
Standalone Binary
If you prefer not to use a package manager:
# macOS (Apple Silicon)
curl -L https://github.com/biomejs/biome/releases/latest/download/biome-darwin-arm64 -o biome
chmod +x biome
# Linux (x64)
curl -L https://github.com/biomejs/biome/releases/latest/download/biome-linux-x64 -o biome
chmod +x biome
Initialize Configuration
npx @biomejs/biome init
This creates a biome.json (or biome.jsonc) in your project root with default settings.
Configuration
Biome's configuration lives in biome.json at your project root. Here is a practical configuration that covers common needs:
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"organizeImports": {
"enabled": true
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf"
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"jsxQuoteStyle": "double",
"semicolons": "always",
"trailingCommas": "all",
"arrowParentheses": "always",
"bracketSpacing": true,
"bracketSameLine": false
}
},
"json": {
"formatter": {
"trailingCommas": "none"
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noExtraBooleanCast": "error",
"noMultipleSpacesInRegularExpressionLiterals": "error",
"noUselessCatch": "error",
"noUselessTypeConstraint": "error"
},
"correctness": {
"noUnusedVariables": "warn",
"noUnusedImports": "warn",
"useExhaustiveDependencies": "warn"
},
"suspicious": {
"noExplicitAny": "warn",
"noConsoleLog": "warn"
},
"style": {
"useConst": "error",
"noNonNullAssertion": "warn",
"useImportType": "error"
},
"performance": {
"noAccumulatingSpread": "warn"
}
}
},
"files": {
"ignore": [
"node_modules",
"dist",
"build",
".next",
"coverage",
"*.min.js"
]
}
}
Configuration Breakdown
vcs: Tells Biome to respect your .gitignore file, so it automatically skips ignored files.
organizeImports: Sorts and groups your import statements. This replaces the eslint-plugin-import sorting rules.
formatter: Global formatting options. indentStyle can be "tab" or "space". lineWidth is the equivalent of Prettier's printWidth.
javascript.formatter: JavaScript-specific formatting options. These map closely to Prettier's options, so migration is straightforward.
linter.rules: Enable rule groups and override individual rules. The recommended preset enables a curated set of rules covering correctness, performance, and style. You can set individual rules to "error", "warn", or "off".
files.ignore: Paths to skip entirely. Combined with vcs.useIgnoreFile, this gives you fine-grained control.
CLI Usage
Formatting
# Format all supported files
npx @biomejs/biome format --write .
# Check formatting without modifying files
npx @biomejs/biome format .
# Format specific files
npx @biomejs/biome format --write src/index.ts src/utils.ts
Linting
# Lint all supported files
npx @biomejs/biome lint .
# Lint and apply safe fixes automatically
npx @biomejs/biome lint --write .
# Lint specific files
npx @biomejs/biome lint src/
Check (Format + Lint + Import Sorting)
The check command runs everything at once:
# Check everything, apply all safe fixes
npx @biomejs/biome check --write .
# Check without modifications (for CI)
npx @biomejs/biome check .
# Check staged files only (for pre-commit hooks)
npx @biomejs/biome check --staged
The check command is what you will use most often. It formats, lints, and organizes imports in a single pass.
Package.json Scripts
Add these to your package.json:
{
"scripts": {
"check": "biome check --write .",
"check:ci": "biome check .",
"format": "biome format --write .",
"lint": "biome lint --write ."
}
}
Migrating from Prettier
Biome achieves over 97% compatibility with Prettier's output. For most projects, switching is seamless.
Step 1: Install Biome
bun add --dev --exact @biomejs/biome
Step 2: Migrate Configuration
Biome provides a migration command that reads your Prettier configuration and generates the equivalent biome.json:
npx @biomejs/biome migrate prettier --write
This reads .prettierrc, .prettierrc.json, prettier.config.js, or the prettier key in package.json and translates the options.
Step 3: Run Biome Format
npx @biomejs/biome format --write .
Review the diff. In most cases, the output is identical to Prettier. Known differences:
- Biome may format some edge cases in template literals slightly differently
- Some parenthesization choices in complex expressions may differ
- These are cosmetic and do not affect semantics
Step 4: Remove Prettier
bun remove prettier eslint-config-prettier eslint-plugin-prettier
rm .prettierrc .prettierignore
Migrating from ESLint
Biome includes a migration path for ESLint as well:
npx @biomejs/biome migrate eslint --write
This reads your .eslintrc or eslint.config.js and maps supported rules to their Biome equivalents. Not every ESLint rule has a Biome counterpart, so the migration command reports which rules could not be mapped.
ESLint Rules Coverage
Biome implements the most commonly used ESLint rules. As of early 2026, Biome covers:
- Most of
eslint:recommended - Key rules from
@typescript-eslint - Import sorting (replaces
eslint-plugin-import) - React hooks rules (replaces
eslint-plugin-react-hooks) - Accessibility rules (replaces
eslint-plugin-jsx-a11y)
Rules that require type information (like @typescript-eslint/no-floating-promises) are not yet supported because Biome does not run TypeScript's type checker. For these rules, you may want to keep tsc --noEmit in your CI pipeline.
Handling Unsupported Rules
After migration, review the output for unmapped rules. For each one, decide:
- Is there a Biome equivalent with a different name? Check the Biome documentation.
- Can you drop the rule? Many ESLint rules are redundant with TypeScript's own checks.
- Is this rule critical? If so, you can keep a minimal ESLint configuration alongside Biome for just those specific rules.
Editor Integration
VS Code
Install the official Biome VS Code extension from the marketplace.
Configure VS Code to use Biome as the default formatter:
{
"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"
},
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
The extension provides:
- Format on save
- Inline lint diagnostics (squiggly lines)
- Quick fixes via
Ctrl+. - Import organization on save
IntelliJ/WebStorm
Install the Biome plugin from the JetBrains marketplace. Configure it as the default formatter in Settings > Languages & Frameworks > Biome.
Neovim
Using nvim-lspconfig:
require("lspconfig").biome.setup({
cmd = { "npx", "@biomejs/biome", "lsp-proxy" },
})
Or with none-ls.nvim for formatting integration. Biome speaks the Language Server Protocol natively, so any editor with LSP support can use it.
Zed
Biome is supported out of the box in Zed. Add to your Zed settings:
{
"formatter": {
"external": {
"command": "npx",
"arguments": ["@biomejs/biome", "format", "--stdin-file-path", "{buffer_path}"]
}
}
}
CI/CD Integration
GitHub Actions
name: Code Quality
on: [push, pull_request]
jobs:
biome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: npx @biomejs/biome check .
That is the entire CI configuration for formatting and linting. One command, one step, fast execution.
Pre-commit Hook
Using Husky and lint-staged:
bun add --dev husky lint-staged
npx husky init
.husky/pre-commit:
npx lint-staged
package.json:
{
"lint-staged": {
"*.{js,ts,jsx,tsx,json,css}": [
"biome check --write --no-errors-on-unmatched"
]
}
}
Or use Biome's built-in staged file support:
# In .husky/pre-commit
npx @biomejs/biome check --staged --write --no-errors-on-unmatched
The --staged flag tells Biome to only check files that are currently staged in git, and it re-stages them after fixing. This is faster and more reliable than lint-staged for Biome specifically.
Lefthook
If you prefer Lefthook over Husky:
# lefthook.yml
pre-commit:
commands:
biome:
glob: "*.{js,ts,jsx,tsx,json,css}"
run: npx @biomejs/biome check --write --no-errors-on-unmatched {staged_files}
stage_fixed: true
Performance
Biome's performance advantage is not marginal -- it is transformational. On a codebase with 1,000 TypeScript files:
| Tool | Format Time | Lint Time | Total |
|---|---|---|---|
| Prettier + ESLint | 8.2s | 12.5s | 20.7s |
| Biome check | 0.3s | - | 0.3s |
The reason is straightforward: Biome is written in Rust, parses each file once, and runs formatting and linting in the same pass. Prettier and ESLint are separate Node.js processes that each parse every file independently.
This speed difference matters most in:
- Pre-commit hooks: Sub-second checks mean developers do not disable the hook
- CI pipelines: Faster feedback loops
- Editor integration: Instant format-on-save with no perceptible delay
- Large monorepos: Where Prettier + ESLint can take minutes
Suppressing Rules
When you need to suppress a specific lint rule for a line or block:
// Single line suppression
// biome-ignore lint/suspicious/noExplicitAny: legacy API requires any
const result: any = legacyFunction();
// Multiple rules on one suppression
// biome-ignore lint/suspicious/noConsoleLog lint/correctness/noUnusedVariables: debugging
const debug = console.log("test");
The suppression comment format is biome-ignore <rule>: <reason>. The reason is mandatory -- Biome will not accept a bare suppression without an explanation. This is a deliberate design choice that encourages thoughtful suppression over blanket ignoring.
Working with Monorepos
Biome supports configuration inheritance, which is useful in monorepos:
my-monorepo/
├── biome.json # Root config (shared settings)
├── packages/
│ ├── frontend/
│ │ └── biome.json # Extends root, adds JSX rules
│ └── backend/
│ └── biome.json # Extends root, adds Node rules
Child biome.json files automatically extend the nearest parent configuration. You can override any setting at any level.
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"extends": ["../../biome.json"],
"linter": {
"rules": {
"suspicious": {
"noConsoleLog": "off"
}
}
}
}
Biome vs. Alternatives
Biome vs. Prettier + ESLint
The incumbent combination. Biome replaces both with better performance and simpler configuration. The trade-off is that Biome does not yet support every ESLint plugin's rules. For most projects, Biome covers enough. For projects with heavy reliance on specialized ESLint plugins (like eslint-plugin-testing-library with custom rules), you may need to keep ESLint for those specific rules.
Biome vs. dprint
dprint is another Rust-based formatter. It is fast and pluggable, but it focuses exclusively on formatting. It does not include linting. If you need both formatting and linting in one tool, Biome is the choice.
Biome vs. oxlint
oxlint (from the oxc project) is a Rust-based linter focused purely on linting. It is extremely fast and covers many ESLint rules. However, it does not include formatting. You could theoretically use dprint + oxlint, but Biome gives you both in one package.
Best Practices
Start with recommended rules: The "recommended": true preset is well-curated. Enable it first, then adjust individual rules based on your team's preferences.
Use check not format + lint separately: The check command is optimized to run everything in a single pass.
Pin the version: Use --save-exact when installing. Biome's rapid development means even minor versions can introduce new rules or formatting changes.
Require suppression reasons: Biome enforces this by default. Do not work around it -- the reasons become documentation for future developers.
Format the entire codebase in one commit: When migrating, format everything at once in a dedicated commit. This makes the formatting commit easy to skip in git blame using git blame --ignore-rev.
Conclusion
Biome represents a generational leap in JavaScript tooling. By combining formatting and linting into a single Rust-powered binary, it eliminates an entire class of configuration headaches while delivering performance that makes code quality checks invisible in your workflow. The migration path from Prettier and ESLint is practical and well-supported. For new projects, there is little reason to reach for the old tools. For existing projects, the migration is worth the effort -- the speed and simplicity pay for themselves within a week.