How to Migrate from ESLint to Biome
·PkgPulse Team
TL;DR
Biome replaces ESLint + Prettier in a single tool and runs 25x faster. Biome covers ~200+ ESLint rules natively — enough for most projects. Migration is 30-60 minutes: install Biome, run biome migrate eslint, review the output, delete .eslintrc. The main limitation: Biome doesn't support custom plugins or rules from the ecosystem (eslint-plugin-react-hooks still needed). Most teams run Biome for the 95% and keep a minimal ESLint config for the remaining plugins.
Key Takeaways
- 25x faster than ESLint — Rust-based, processes files in parallel
- Replaces both ESLint + Prettier — one tool for linting and formatting
- ~200+ rules built-in — covers
recommendedconfigs for most plugins - No plugin system yet — third-party plugins not supported
biome migrate eslint— official command that auto-converts your config
Step 1: Install Biome
npm install -D --save-exact @biomejs/biome
# Initialize config
npx @biomejs/biome init
# This creates biome.json at project root
Step 2: Run the Migration Tool
# Auto-convert your ESLint config to Biome
npx @biomejs/biome migrate eslint --write
# This command:
# 1. Reads your .eslintrc / eslint.config.js
# 2. Maps rules to Biome equivalents
# 3. Updates biome.json with matched rules
# 4. Reports rules it couldn't migrate
# Example output:
# ✔ Migrated 45 rules
# ℹ 3 rules have no Biome equivalent:
# - react-hooks/rules-of-hooks (use eslint-plugin-react-hooks)
# - import/no-cycle (no equivalent)
# - custom-rules/my-rule (custom plugin)
Step 3: Review biome.json
// biome.json — after migration
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"useAltText": "error",
"noAutofocus": "warn"
},
"complexity": {
"noExtraBooleanCast": "error",
"noMultipleSpacesInRegularExpressionLiteral": "error"
},
"correctness": {
"noUnusedVariables": "error",
"useExhaustiveDependencies": "warn" // replaces react-hooks/exhaustive-deps
},
"security": {
"noDangerouslySetInnerHtml": "warn"
},
"style": {
"noVar": "error",
"useConst": "error",
"useSingleVarDeclarator": "error"
},
"suspicious": {
"noConsoleLog": "warn",
"noDebugger": "error",
"noDoubleEquals": "error"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"javascript": {
"formatter": {
"quoteStyle": "single", // Was: "singleQuote": true in Prettier
"trailingCommas": "es5", // Was: "trailingComma": "es5" in Prettier
"semicolons": "always" // Was: "semi": true in Prettier
}
},
"files": {
"ignore": [
"dist/**",
"build/**",
"node_modules/**",
"*.min.js"
]
}
}
Step 4: Update package.json Scripts
// Before (ESLint + Prettier):
{
"scripts": {
"lint": "eslint src/ --ext .ts,.tsx --report-unused-disable-directives",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write src/",
"format:check": "prettier --check src/"
}
}
// After (Biome only):
{
"scripts": {
"lint": "biome lint --apply .",
"format": "biome format --write .",
"check": "biome check --apply .", // Lint + format in one command
"ci": "biome ci ." // CI: no --apply (fail on issues)
}
}
Step 5: Handle Unmigrated Rules
# Rules with no Biome equivalent need to stay in ESLint
# Common examples:
# - eslint-plugin-react-hooks (rules-of-hooks, exhaustive-deps)
# → react-hooks/rules-of-hooks: partial Biome equivalent (correctness/useHookAtTopLevel)
# → react-hooks/exhaustive-deps: correctness/useExhaustiveDependencies ✅ covered
# - eslint-plugin-import (no-cycle, order)
# → import ordering: Biome's organizeImports handles basic sorting
# → circular deps: no equivalent
# - Custom company rules → no equivalent
// Minimal .eslintrc for rules Biome can't handle
// Keep this tiny — Biome handles the rest
{
"plugins": ["react-hooks", "import"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"import/no-cycle": "warn"
},
"ignorePatterns": [
// Ignore files already covered by Biome
// This prevents double-linting
]
}
Step 6: CI Configuration
# .github/workflows/lint.yml
name: Lint & Format
on: [push, pull_request]
jobs:
biome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest
- name: Run Biome CI
run: biome ci .
# biome ci: no --apply flag, fails on any issue
# Reports: linting errors + formatting differences
ESLint Rule → Biome Rule Mapping
| ESLint Rule | Biome Equivalent |
|---|---|
no-unused-vars | correctness/noUnusedVariables |
no-var | style/noVar |
prefer-const | style/useConst |
no-console | suspicious/noConsoleLog |
no-debugger | suspicious/noDebugger |
eqeqeq | suspicious/noDoubleEquals |
no-extra-boolean-cast | complexity/noExtraBooleanCast |
react-hooks/exhaustive-deps | correctness/useExhaustiveDependencies |
jsx-a11y/alt-text | a11y/useAltText |
jsx-a11y/no-autofocus | a11y/noAutofocus |
no-dangerously-set-inner-html | security/noDangerouslySetInnerHtml |
VS Code Integration
// .vscode/settings.json
{
// Disable ESLint and Prettier extensions for this project
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
// Disable conflicting extensions
"[javascript]": { "editor.defaultFormatter": "biomejs.biome" },
"[typescript]": { "editor.defaultFormatter": "biomejs.biome" },
"[typescriptreact]": { "editor.defaultFormatter": "biomejs.biome" },
"[json]": { "editor.defaultFormatter": "biomejs.biome" }
}
Performance Comparison
# Real benchmark: 500 TypeScript files
# ESLint (JavaScript):
# Cold lint: 8.2 seconds
# With cache: 2.1 seconds
# Biome (Rust):
# Cold lint: 0.3 seconds ← 27x faster
# Always cold (no cache needed — it's fast enough)
# Format: 0.1 seconds
# Why Biome is faster:
# 1. Written in Rust — 10-100x faster parser
# 2. Parallel file processing
# 3. No plugin overhead
# 4. No Node.js startup cost
Compare Biome and ESLint package health on PkgPulse.
See the live comparison
View biome vs. eslint on PkgPulse →