March 26, 20267 min read

Biome vs ESLint — The New JavaScript Linter War

How Biome compares to ESLint + Prettier — speed benchmarks, rule coverage, migration path, and whether it's ready to replace your current linting setup.

biome eslint linting formatting javascript
Ad 336x280

The standard JavaScript linting setup has been ESLint + Prettier for years. ESLint catches bugs and enforces code patterns. Prettier formats code. They work together through eslint-config-prettier (to disable ESLint formatting rules) and eslint-plugin-prettier (to run Prettier as an ESLint rule). This stack works, but it's slow, configuration-heavy, and requires maintaining compatibility between two separate tools.

Biome is the successor to Rome (the abandoned JavaScript toolchain by the creator of Babel). It's a single tool written in Rust that handles both linting and formatting. One config file. One install. One command. And it's 20-100x faster than ESLint + Prettier.

Speed

The performance difference isn't subtle.

# Benchmarking on a large TypeScript monorepo (~3000 files)

# ESLint + Prettier
time npx eslint . && npx prettier --check .
# real: 45.2s

# Biome
time npx @biomejs/biome check .
# real: 0.8s

That's not a typo. Biome processes the entire codebase in under a second where ESLint takes nearly a minute. The difference comes from:

  1. Rust vs JavaScript. ESLint parses, traverses ASTs, and runs rules in JavaScript. Biome does it in Rust.
  2. Parallel processing. Biome uses all CPU cores. ESLint is single-threaded (though ESLint 9 added some parallelism).
  3. Single parse. Biome parses each file once for both linting and formatting. ESLint + Prettier parse files separately.
  4. No plugin overhead. ESLint loads and initializes plugins at startup. Biome's rules are compiled into the binary.

Setup

# Install
bun add -d @biomejs/biome

# Initialize config
npx @biomejs/biome init

This creates biome.json:

{
  "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "double",
      "semicolons": "always",
      "trailingCommas": "all"
    }
  }
}
// package.json scripts
{
  "scripts": {
    "lint": "biome check .",
    "lint:fix": "biome check --write .",
    "format": "biome format --write ."
  }
}
# Lint and format
npx @biomejs/biome check .              # Report issues
npx @biomejs/biome check --write .      # Fix all auto-fixable issues
npx @biomejs/biome format --write .     # Format only
npx @biomejs/biome lint .               # Lint only

Rule Coverage

Biome has been steadily implementing ESLint rules. Here's where it stands:

// biome.json — configuring specific rules
{
  "linter": {
    "rules": {
      "recommended": true,
      "complexity": {
        "noForEach": "warn",
        "useSimplifiedLogicExpression": "error"
      },
      "suspicious": {
        "noExplicitAny": "error",
        "noConsoleLog": "warn"
      },
      "correctness": {
        "noUnusedVariables": "error",
        "noUnusedImports": "error",
        "useExhaustiveDependencies": "warn"
      },
      "style": {
        "noNonNullAssertion": "warn",
        "useConst": "error",
        "useTemplate": "error"
      },
      "nursery": {
        "useSortedClasses": "warn"
      }
    }
  }
}
Rules Biome has:
  • Most ESLint core rules
  • Most @typescript-eslint rules
  • React hooks rules (useExhaustiveDependencies)
  • Import sorting
  • Tailwind class sorting (nursery)
  • Accessibility rules (JSX a11y equivalents)
Rules Biome is missing (as of early 2026):
  • Some niche ESLint plugin rules
  • Full parity with eslint-plugin-react (partial coverage)
  • Some @typescript-eslint type-aware rules (requires type information from TS compiler)
  • Plugin ecosystem — you can't write custom Biome rules in JavaScript

Migration from ESLint

Biome includes a migration tool:

# Automatically convert ESLint config to Biome config
npx @biomejs/biome migrate eslint --write

# Convert Prettier config too
npx @biomejs/biome migrate prettier --write

The migration reads your .eslintrc (or eslint.config.js) and .prettierrc, maps rules to Biome equivalents, and updates biome.json. Rules without Biome equivalents are listed so you can decide what to do.

Practical migration steps:
  1. Install Biome alongside ESLint (don't remove ESLint yet)
  2. Run biome migrate eslint --write
  3. Run biome check . and compare output to eslint .
  4. Identify any rules that ESLint catches but Biome doesn't
  5. If the missing rules aren't critical, remove ESLint
  6. If they are, keep ESLint for those specific rules and use Biome for everything else

Biome vs ESLint Feature Comparison

FeatureESLint + PrettierBiome
SpeedSlow (~30-60s for large projects)Fast (~0.5-2s for large projects)
LanguagesJS, TS (+ plugins for Vue, Svelte, etc.)JS, TS, JSX, TSX, JSON, CSS
FormattingPrettier (separate tool)Built-in
Config.eslintrc + .prettierrc + pluginsSingle biome.json
Plugin systemMassive ecosystemNo custom plugins
Type-aware rulesYes (with @typescript-eslint)Limited
Auto-fixMost rulesMost rules
Editor supportExcellent (VS Code, all editors)Good (VS Code, IntelliJ)
CI integrationStandardStandard
Custom rulesWrite your own in JSNot supported
Import sortingeslint-plugin-importBuilt-in
Monorepo supportPer-package configsCascading configs

The Plugin Problem

This is the real question. ESLint's power comes from its plugin ecosystem. Your project probably uses some of these:

  • @typescript-eslint — TypeScript-specific rules
  • eslint-plugin-react — React rules
  • eslint-plugin-react-hooks — Hooks rules
  • eslint-plugin-import — Import ordering and validation
  • eslint-plugin-jsx-a11y — Accessibility rules
  • eslint-plugin-tailwindcss — Tailwind-specific rules
  • eslint-plugin-testing-library — Testing library rules
Biome has built-in equivalents for many of these, but not all. And there's no way to add custom rules — Biome rules are compiled Rust code, not JavaScript plugins. If your ESLint config is mostly standard rules: Biome can replace it today. If you rely heavily on niche plugins: You'll lose those rules by switching.

Editor Integration

// .vscode/settings.json
{
  // Use Biome as default formatter
  "editor.defaultFormatter": "biomejs.biome",
  "[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" },

// Format and fix on save
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
}
}

The VS Code extension provides:


  • Format on save

  • Inline diagnostics (squiggly lines)

  • Code actions (quick fixes)

  • Import sorting on save


CI Setup

# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: biomejs/setup-biome@v2
      - run: biome ci .
biome ci is the CI-specific command — it reports issues without writing fixes and exits with a non-zero code if there are errors.

When to Switch

Switch to Biome if:
  • Speed matters to you (especially in CI and pre-commit hooks)
  • You want simpler configuration (one tool, one config)
  • Your ESLint config uses mostly standard rules
  • You're starting a new project
Stay with ESLint if:
  • You depend on specific ESLint plugins that Biome doesn't support
  • You've written custom ESLint rules
  • You need type-aware linting rules
  • Your team's ESLint config is well-established and working fine
Use both if:
  • You want Biome for formatting + basic linting (fast)
  • You want ESLint for specific plugin rules only (targeted)
// biome.json — handle formatting and basic linting
{
  "formatter": { "enabled": true },
  "linter": {
    "rules": {
      "recommended": true,
      "correctness": {
        "noUnusedVariables": "error",
        "noUnusedImports": "error"
      }
    }
  }
}
// eslint.config.js — only the rules Biome can't handle
import tseslint from "typescript-eslint";

export default tseslint.config({
extends: [tseslint.configs.strictTypeChecked],
rules: {
// Only type-aware rules that Biome can't do
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/await-thenable": "error",
},
});

The JavaScript tooling ecosystem is consolidating around Rust-based tools. Biome for linting/formatting, Vite (using esbuild/SWC) for building, Turbopack for Next.js. The direction is clear — JavaScript tools written in faster languages. Biome is the most mature entry in the linting space, and it's only getting better. More tooling guides at CodeUp.

Ad 728x90