How to migrate from ESLint and Prettier to Biome in 2026
Two commands migrate most of your config automatically. Covers install, rule mapping, editor setup, CI, uninstall — and gaps that block some teams.
By Ethan
2,034 words · 11 min read
Two commands handle most of the migration automatically: biome migrate eslint --write and biome migrate prettier --write. The remaining work is gap analysis — checking which rules you rely on that Biome doesn’t cover yet. For most TypeScript projects, that gap is small. For teams that depend on eslint-plugin-security, no-floating-promises, or framework-specific plugins, it may be a hard blocker.
This guide is a full walkthrough on Biome 2.4.15 (released 2026-05-09), covering install through CI. It takes about two hours on a standard project. A large monorepo may take a day.
Who this is for
TypeScript and JavaScript developers running ESLint 8.x or 9.x plus Prettier 2.x or 3.x who want a single, faster tool. If you rely on eslint-plugin-security, heavy Vue/Svelte/Astro framework linting, or custom ESLint rules in production, read When not to migrate before you start — you may need to stay on ESLint.
For a detailed comparison of features, rule coverage, and benchmarks before you commit to the switch, see Biome vs ESLint 2026.
Why bother
The honest case for switching is three things.
Speed. Biome is a single Rust binary that runs fully in parallel. ESLint is single-threaded by default and adds Node.js startup overhead across its plugin chain. Independent benchmarks from BetterStack measured 10–15× faster linting on a 500-file project. A production case study from FireUp.pro clocked a large TypeScript monorepo pre-commit hook dropping from 27.2 s to 1.8 s — a 15× wall-clock improvement. Biome’s own benchmarks claim 56× faster linting and 40× faster formatting on 10,000 files; those figures are self-reported upper bounds, not third-party verified.
One dependency instead of six. A typical ESLint + Prettier setup installs ESLint core, a TypeScript parser, @typescript-eslint, at least two plugins, eslint-config-prettier, and Prettier itself. Biome replaces all of them with one package.
Unified config. One biome.json controls formatting and linting. No more reconciling .eslintrc and .prettierrc. No more eslint-config-prettier to suppress formatting conflicts.
Prerequisites
Before you start:
- Node.js installed (needed to run the migration tool; Biome itself doesn’t require Node at runtime)
- ESLint 8.x or 9.x in your project
- Prettier 2.x or 3.x in your project (optional — skip the Prettier migration step if you’re not using it)
- Your ESLint config is not in YAML format (
.eslintrc.yaml/.eslintrc.yml) — convert it to JSON first if it is; see Step 2 - Your Prettier config is not in JavaScript format (
.prettierrc.js) — convert to.prettierrc.jsonfirst if it is
Step 1: Install Biome
# npm
npm install --save-dev --save-exact @biomejs/biome
# pnpm
pnpm add --save-dev --save-exact @biomejs/biome
# bun
bun add --dev --exact @biomejs/biome
The --save-exact flag is recommended by Biome because formatting output can change between patch versions, and you want deterministic CI output. Pin the version.
Once installed, initialize the config:
npx @biomejs/biome init
# For JSON with comments (JSONC):
npx @biomejs/biome init --jsonc
This creates biome.json at the project root with sensible defaults. You’ll populate it in the next steps.
Failure mode: if npx @biomejs/biome init fails with a permissions error, check that node_modules/.bin is writable. On monorepos, ensure you’re running this from the workspace root.
Step 2: Prepare your ESLint config
biome migrate eslint supports .eslintrc.json (including extends and shared configs), flat config files (.js, .cjs, .mjs), and .eslintignore. It does not support YAML-based configs.
If your config is in YAML, convert it first:
# Quick way — yq must be installed
yq -o=json .eslintrc.yaml > .eslintrc.json
# Then delete the YAML file
rm .eslintrc.yaml
Also watch for cyclic extends chains (configs that require() other shared configs in a loop). The migration tool will fail on these. Flatten the config manually before running the migration.
Step 3: Migrate your ESLint rules
biome migrate eslint --write
This reads your ESLint config, converts matching rules to Biome equivalents, and writes the result into biome.json. Run it from the directory that contains your ESLint config.
If you want Biome to also adopt “inspired” rules — rules Biome derived from ESLint rules but didn’t map directly — add --include-inspired:
biome migrate eslint --write --include-inspired
Inspired rules include useArrowFunction (from prefer-arrow-callback), useImportType (from @typescript-eslint/consistent-type-imports), and useNamingConvention (a subset of @typescript-eslint/naming-convention). Check each one before enabling — they’re off by default for a reason.
After running: open biome.json and inspect the linter.rules section. The migration tool converts what it recognizes and silently drops what it doesn’t. Compare your original ESLint rule list against what landed in biome.json. Any rule missing from the output is either a gap or an inspired rule that needs --include-inspired.
Failure mode: if the command exits with Cannot read ESLint plugin, ensure Node.js is in your PATH. The tool loads plugins during migration even though Biome itself doesn’t need Node at runtime.
Step 4: Know your gaps
Not every ESLint rule has a Biome equivalent. The ones that matter most for TypeScript projects:
| ESLint rule | Biome status |
|---|---|
no-unused-vars | ✅ noUnusedVariables |
no-undef | ✅ noUndeclaredVariables |
eqeqeq | ✅ noDoubleEquals |
@typescript-eslint/no-explicit-any | ✅ noExplicitAny |
@typescript-eslint/prefer-optional-chain | ✅ useOptionalChain |
no-shadow | ❌ No equivalent |
no-magic-numbers | ❌ No equivalent |
@typescript-eslint/no-floating-promises | ⚠️ Partial coverage via type-aware linting (v2.0+) — exact parity with no-floating-promises not reached |
@typescript-eslint/no-misused-promises | ❌ No equivalent |
eslint-plugin-security | ❌ No equivalent |
Biome v2.0 introduced type-aware linting without requiring the TypeScript compiler, which catches the most common floating-promise cases. Full parity with no-floating-promises and no-misused-promises does not exist yet.
If those rules are non-negotiable on your team, you have two options: keep ESLint for those rules only (hybrid mode) or accept the gap. Hybrid mode is workable but adds back the complexity you were trying to remove.
Source: Biome rules sources page and Biome linter docs.
Step 5: Migrate your Prettier config
biome migrate prettier --write
This reads .prettierrc.json (or .prettierrc in JSON format) and writes the equivalent formatter options into biome.json.
The full option mapping:
| Prettier | Biome | Default (Biome) |
|---|---|---|
printWidth | formatter.lineWidth | 80 |
tabWidth | formatter.indentWidth | 2 |
useTabs | formatter.indentStyle | "tab" |
endOfLine | formatter.lineEnding | "lf" |
singleQuote | javascript.formatter.quoteStyle | "double" |
jsxSingleQuote | javascript.formatter.jsxQuoteStyle | "double" |
trailingComma | javascript.formatter.trailingCommas | "all" |
semi | javascript.formatter.semicolons | "always" |
arrowParens | javascript.formatter.arrowParentheses | "always" |
bracketSpacing | javascript.formatter.bracketSpacing | true |
bracketSameLine | javascript.formatter.bracketSameLine | false |
Critical check: Biome defaults to tabs for indentation. If your .prettierrc doesn’t specify useTabs: false explicitly, the migration tool may set formatter.indentStyle: "tab" when you expected spaces. Open biome.json after running and confirm formatter.indentStyle matches your intent.
Failure mode: JavaScript-format Prettier configs (.prettierrc.js, .prettierrc.cjs) are not reliably converted. Convert to .prettierrc.json first, run the migration, then delete the JS config.
Source: Biome reference configuration.
Step 6: Update editor integration
VS Code
Install the official Biome extension from the VS Code Marketplace. Add this to your workspace .vscode/settings.json to enable format-on-save:
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"[javascript]": { "editor.defaultFormatter": "biomejs.biome" },
"[typescript]": { "editor.defaultFormatter": "biomejs.biome" },
"[json]": { "editor.defaultFormatter": "biomejs.biome" }
}
Known issue: the VS Code import organizer has a documented bug where complex import graphs can be mangled. Kitty Giraudel’s 40-package monorepo migration notes the import organizer “mangles files when rewriting imports” (referencing GitHub issue #1570). If you rely on import sorting, test it on your actual codebase before enabling. You can disable just the import sorter in VS Code settings:
{
"biome.organizeImports": false
}
Also note: the VS Code extension only applies safe fixes automatically. Unsafe fixes — like noVar converting var to const — require running biome check --write --unsafe from the CLI.
JetBrains / WebStorm
Install the Biome plugin from the JetBrains Marketplace (biomejs.biome). Once installed, go to Settings → Languages & Frameworks → JavaScript → Biome and point it at the local binary (node_modules/@biomejs/biome/bin/biome). Enable “Run on save” for formatting.
Step 7: Update CI
Replace your ESLint + Prettier CI steps with a single biome ci call:
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest
- name: Run Biome
run: biome ci .
biome ci is the non-interactive variant of biome check. It exits non-zero on any violation and does not auto-fix. The biomejs/setup-biome action caches the Biome binary so repeated CI runs don’t re-download it.
Pre-commit hooks (if you’re using Husky + lint-staged): Biome has a native --staged flag that processes only staged files, so lint-staged is no longer necessary:
# .husky/pre-commit
npx biome check --staged
The --staged flag is available since Biome v1.7.0.
Large repos: enable VCS integration in biome.json so Biome only processes changed files:
{
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
}
}
Source: Biome CI docs via AppSignal migration guide.
Step 8: Uninstall ESLint and Prettier
Only do this after Step 9 (verifying parity). Once you’re satisfied:
# ESLint core
npm uninstall eslint
# ESLint plugins — adjust to your actual deps
npm uninstall \
@typescript-eslint/eslint-plugin \
@typescript-eslint/parser \
eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-plugin-jsx-a11y \
eslint-plugin-import \
eslint-plugin-unicorn \
eslint-config-prettier \
eslint-config-airbnb \
eslint-config-airbnb-base
# Prettier
npm uninstall prettier prettier-plugin-tailwindcss
# Remove config files
rm -f .eslintrc.json .eslintrc.js .eslintignore
rm -f .prettierrc .prettierrc.json .prettierignore
Double-check package.json for any remaining eslint or prettier references in devDependencies after running these — some transitive deps may have hoisted them.
Step 9: Verify parity
Run Biome across the full codebase:
npx @biomejs/biome check --write .
The --write flag applies safe auto-fixes. Commit those changes as a single “biome: auto-fix” commit so your diff stays readable.
Then run without --write to see what’s left:
npx @biomejs/biome check .
Any remaining violations are either things you need to fix manually or rules you should disable in biome.json because your project intentionally doesn’t follow them. Work through the list. When biome check . exits clean, you’re done.
For rules that need bulk unsafe fixing (like converting all var to const):
npx @biomejs/biome check --write --unsafe .
Review the diff carefully before committing — unsafe fixes change semantics.
When not to migrate
Don’t migrate if any of these apply to your project:
eslint-plugin-security is required. There is no Biome equivalent as of v2.4.15. Security teams that depend on these rules need to stay on ESLint.
@typescript-eslint/no-floating-promises or no-misused-promises are non-negotiable. Biome v2.0 added type-aware linting with partial coverage of floating-promise cases, but full parity doesn’t exist — exact parity with no-floating-promises has not been reached. If your async-heavy codebase relies on 100% coverage of unhandled promises, the gap matters.
Custom ESLint rules in production. Biome v2.0 shipped a GritQL-based plugin system, but it’s first-iteration. Don’t assume your custom rules port cleanly without testing them. If they’re complex, the port may cost more than the speed gain.
Vue/Svelte/Astro with heavy framework-specific linting. Biome v2.4 improved framework parsing but community-reported gaps remain. eslint-plugin-next is partially supported; eslint-plugin-vue, eslint-plugin-svelte, and eslint-plugin-astro still have holes. Test on your actual codebase before committing.
Your ESLint config is distributed as a shared npm package. If you maintain an org-wide ESLint config package that dozens of projects consume, the migration scope expands considerably. The speed gain may not justify the coordination cost.
SCSS linting. Biome doesn’t lint SCSS. It’s on the 2026 roadmap but not shipped. If you use stylelint for CSS/SCSS and ESLint for JS, the ESLint side could still migrate; the stylelint side is untouched.
If you’re doing a broader toolchain upgrade at the same time, How to migrate from Jest to Vitest and How to migrate from Webpack to Vite cover the test runner and bundler sides of the same modernization.
References
- Biome official migration guide
- Biome linter rules sources (ESLint mapping)
- Biome reference configuration
- Biome v2.0 release notes (type-aware linting)
- Biome 2026 roadmap
- BetterStack: Biome vs ESLint benchmarks
- FireUp.pro: pre-commit hooks 15× faster case study
- Kitty Giraudel: from ESLint and Prettier to Biome
- AppSignal: migrating a JS project to BiomeJS