← All articles
LANGUAGES Biome: The Fast Formatter and Linter for JavaScript ... 2026-02-14 · 9 min read · biome · javascript · typescript

Biome: The Fast Formatter and Linter for JavaScript and TypeScript

Languages 2026-02-14 · 9 min read biome javascript typescript linting formatting eslint prettier dx

Biome: The Fast Formatter and Linter for JavaScript and TypeScript

Biome toolchain logo

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:

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:

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:

  1. Is there a Biome equivalent with a different name? Check the Biome documentation.
  2. Can you drop the rule? Many ESLint rules are redundant with TypeScript's own checks.
  3. 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:

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:

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.