Migrate, Upgrade, and Compare Formatting

Purpose and Scope

This page explains the workflows Biome provides when a project is moving between toolchains or versions. In user terms, there are three related jobs: migrate configuration from existing ESLint or Prettier setup, update a Biome configuration after a breaking release, and compare formatting output while developing or evaluating formatter behavior. The public documentation presents the shortest path as running biome migrate eslint --write and biome migrate prettier --write, while the source separates the command wrapper, migration execution, ESLint loading, rule conversion, reusable migration analyzer, upgrade command, and an internal formatting comparison package.

Sources: crates/biome_cli/src/commands/migrate.rs, crates/biome_cli/src/execute/migrate.rs, crates/biome_cli/src/execute/migrate/eslint.rs, crates/biome_cli/src/execute/migrate/eslint_to_biome.rs, crates/biome_migrate/src/lib.rs

Migration is intentionally configuration-centered rather than a general source-code rewrite. The CLI command requires a Biome configuration file path before it creates an execution object; if no configuration file is found, it prints a message pointing the user to the Getting Started guide and returns a migration diagnostic. This matters because Biome migrations are applied against a known project configuration, and repository code treats the operation as a custom execution whose write access depends on --write or --fix. The command does not target VCS state and uses a minimal known-files scan instead of a broader project crawl.

Sources: crates/biome_cli/src/commands/migrate.rs

Relevant Source Files

  • crates/biome_cli/src/commands/migrate.rs - Defines the CLI payload for migrate, write and fix behavior, execution metadata, configuration-file preconditions, and incompatible argument checks.
  • crates/biome_cli/src/execute/migrate.rs - Implements the migration runner that iterates root and nested configuration files, invokes file-level migration, and prints migration result diagnostics.
  • crates/biome_cli/src/execute/migrate/eslint.rs - Loads ESLint flat, legacy, package.json, and ignore-file inputs, including priority ordering and Node.js-backed JavaScript configuration loading.
  • crates/biome_cli/src/execute/migrate/eslint_to_biome.rs - Converts ESLint rule and plugin configuration into Biome configuration, tracks unsupported rule reasons, and handles selected rule options.
  • crates/biome_cli/src/commands/upgrade.rs - Implements biome upgrade, install-source detection, npm/Homebrew/standalone behavior, and upgrade diagnostics.
  • crates/biome_migrate/src/lib.rs - Provides the analyzer-backed migration engine for JSON configuration files and exposes migrate_configuration.
  • packages/prettier-compare/README.md - Documents the internal formatter comparison tool, supported inputs, watch mode, and terminal UI behavior.
  • packages/prettier-compare/src/biome.ts - Integrates the comparison tool with @biomejs/js-api, applies formatter configuration, returns output, IR, and diagnostics.
  • packages/prettier-compare/src/prettier.ts - Provides the Prettier side of the comparison package so Biome and Prettier outputs can be viewed side by side.

Migration Command Flow

At the CLI boundary, migration is represented by MigrateCommandPayload. The payload carries whether the command should write, whether fix mode is active, an optional migration subcommand, and the resolved configuration file and directory paths. should_write() returns true when either write or fix is enabled, and check_incompatible_arguments() delegates to the same fix-file compatibility helper used by other commands. This makes migrate feel consistent with commands such as format or check, while still having its own execution category, summary phrase, and configuration-diagnostic behavior.

Sources: crates/biome_cli/src/commands/migrate.rs

The execution layer builds a MigratePayload with the CLI session, project key, write flag, root configuration path, optional subcommand, and nested configuration files. The runner stores the root configuration and nested configuration paths in a BTreeSet, then migrates each file and accumulates outcomes into a MigrationResultDiagnostic. The diagnostic category is configuration, the severity is informational, and the message is rendered as “Migration results:”. That structure is useful for workspaces because a migration may touch more than one configuration file, while still reporting the final status in a single CLI-visible diagnostic.

Sources: crates/biome_cli/src/execute/migrate.rs

The reusable migration engine lives in biome_migrate. It is built on the analyzer infrastructure for JSON language roots, not on ad hoc string manipulation. migrate_configuration calls analyze_with_inspect_matcher, which builds a rule registry from a migration registry, inserts an IsRoot service, constructs an analyzer with metadata, and runs it over the configuration root. Migration rules can therefore emit analyzer signals and rule actions, while parsing and application continue to use the same concepts as other Biome analyzer subsystems. This design keeps version-to-version configuration migrations close to normal analyzer rule machinery.

Sources: crates/biome_migrate/src/lib.rs

ESLint and Prettier Migration

The ESLint migration path starts by finding the highest-priority ESLint configuration in the current working directory. The loader first checks flat configuration files named ./eslint.config.js, ./eslint.config.mjs, and ./eslint.config.cjs. It then checks legacy files in priority order, including ./.eslintrc.js, ./.eslintrc.cjs, YAML variants, .eslintrc.json, and .eslintrc. If those are not found, it attempts to read ESLint configuration embedded in package.json. The loader also names .eslintignore as the ignore file and documents that JavaScript configuration extraction is performed by invoking Node.js.

Sources: crates/biome_cli/src/execute/migrate/eslint.rs

Conversion from ESLint to Biome is rule-aware. The eslint_to_biome module relies on generated rule mapping from Biome rule metadata, plus hand-written handling for Biome rules that have options. MigrationOptions controls whether inspired rules and nursery rules should be migrated. MigrationResults records the migrated ESLint path, whether the Biome configuration is updated, and rules that could not be migrated automatically. Unsupported rules are not collapsed into a single bucket; they are classified as stylistic conflicts, formatter-covered behavior, formatter options, known sources not yet implemented, unknown sources, or rules covered by a different Biome rule.

Sources: crates/biome_cli/src/execute/migrate/eslint_to_biome.rs

For users, this explains why an ESLint migration may succeed while still reporting manual follow-up. Biome intentionally uses different rule naming conventions and sometimes different rule boundaries than ESLint. A source rule such as an ESLint plugin rule may map directly, may map to a formatter option, may be redundant because the formatter already enforces the behavior, or may be unsupported. The migration result should therefore be read as a porting report, not only as a pass/fail status. Run with --write when you want the configuration file updated, and inspect the diagnostic output for rules or settings requiring judgment.

Sources: crates/biome_cli/src/execute/migrate/eslint_to_biome.rs, crates/biome_cli/src/execute/migrate.rs

biome migrate eslint --write
biome migrate prettier --write

Upgrade Behavior

biome upgrade is a separate workflow from biome migrate. Upgrade updates the Biome executable itself when the installation source supports in-place upgrades, while migration updates project configuration. The command first rejects execution when the BIOME_BINARY environment variable is set, because an overridden binary should be upgraded directly. It then reads the current executable path and detects the installation source. The supported paths are intentionally conservative: npm-distributed binaries are rejected with guidance to upgrade @biomejs/biome using the same package manager, Homebrew installations delegate to brew upgrade biome, and standalone installations use GitHub release information.

Sources: crates/biome_cli/src/commands/upgrade.rs

The upgrade code also shows the trust boundary for installer-specific behavior. Homebrew upgrade is delegated to the brew executable with HOMEBREW_NO_AUTO_UPDATE=1, and errors are specialized when brew is missing or exits unsuccessfully. Standalone upgrade logic is described as downloading the latest release from GitHub and replacing the current executable, using constants for the biomejs/biome repository and the latest-version endpoint at https://biomejs.dev/api/versions/latest.txt. If the install source cannot be determined, the command returns a diagnostic telling the user to upgrade with the original installer or package manager.

Sources: crates/biome_cli/src/commands/upgrade.rs

A practical upgrade sequence is therefore two-step for many projects. First, update the installed package or binary using the correct package manager or installer. Second, run migration on the project configuration so renamed options, removed legacy behavior, and breaking-change mitigations can be applied. The official v2 guide follows that shape by installing an exact version of @biomejs/biome and then running biome migrate --write. If the migration output says manual changes are needed, treat those messages as part of the upgrade checklist rather than as optional noise.

Sources: crates/biome_cli/src/commands/upgrade.rs, crates/biome_cli/src/commands/migrate.rs

npm install --save-dev --save-exact @biomejs/biome@2.0.6
npx @biomejs/biome migrate --write

Formatter Comparison Tool

The packages/prettier-compare package is an internal developer tool for inspecting formatting differences between Prettier and Biome. Its README describes a CLI and React/OpenTUI terminal interface that compares formatted output and intermediate representation side by side. It accepts a snippet argument, a file through --file, or stdin; it can auto-detect language from a file extension or accept --language; and it includes diagnostics output for syntax errors from both tools. This is not the end-user migration command, but it supports formatter development and migration confidence by making behavioral differences visible.

Sources: packages/prettier-compare/README.md, packages/prettier-compare/src/biome.ts, packages/prettier-compare/src/prettier.ts

The Biome integration uses @biomejs/js-api. formatWithBiome creates or reuses a Biome instance with the Node distribution, opens a project, applies formatter configuration, and calls formatContent with debug: true. The returned result includes formatted content, formatter IR when available, and diagnostics normalized into description and severity strings. The applied configuration sets two-space indentation and enables experimental full HTML support with HTML formatter options. If formatting throws, the function returns the original code, empty IR, and an error diagnostic instead of crashing the comparison UI.

Sources: packages/prettier-compare/src/biome.ts

bun packages/prettier-compare/bin/prettier-compare.js "const x={a:1,b:2}"
bun packages/prettier-compare/bin/prettier-compare.js -l ts "const x: number = 1"
bun packages/prettier-compare/bin/prettier-compare.js -f src/example.tsx
echo "const x = 1" | bun packages/prettier-compare/bin/prettier-compare.js -l js

Watch mode is aimed at formatter contributors. The README says the tool can rebuild the Biome WASM build on Rust changes with debounce and spinner feedback, and notes that bun --hot is needed if the bin script should reload the WASM after rebuilding. Before running the tool, the WASM build must be available; the documented paths are using --rebuild or running just build-wasm-node-dev. This makes the package a useful bridge between Rust formatter work and JavaScript-facing output inspection.

Sources: packages/prettier-compare/README.md

Compact Reference

SurfaceConcrete entry pointBehavior
Migrate command payloadMigrateCommandPayloadCarries write, fix, optional MigrateSubCommand, and resolved configuration paths.
Migrate executionMigrateExecutionUses category migrate, requires write access only when requested, and summarizes checked versus migrated configuration.
Migration runnerrun(MigratePayload)Migrates root and nested configuration files and prints MigrationResultDiagnostic.
ESLint loaderread_eslint_configLooks for flat configs, legacy configs, then package.json; recognizes .eslintignore.
ESLint conversionMigrationOptions, MigrationResults, UnsupportedRuleReasonMaps known rules and explains unsupported or formatter-covered rules.
Migration enginemigrate_configurationRuns JSON configuration migration through analyzer rules.
Upgrade commandupgradeRejects npm binaries and BIOME_BINARY, delegates Homebrew, upgrades standalone releases.
Comparison toolformatWithBiomeFormats content through @biomejs/js-api and returns output, IR, and diagnostics.

Next Steps

For project migration, start with the targeted subcommands and use --write only when you are ready to update configuration files. Review the printed migration results, especially unsupported ESLint rules and formatter-covered settings, before deleting old configuration. For version upgrades, update the installed Biome binary or package first, then run biome migrate --write and follow any manual diagnostic advice. For formatter investigation, use packages/prettier-compare from the repository root after building the WASM package. Related pages: cli-reference, configure-biome, formatter, and linter-overview-and-rules.