ESLint 9 killed .eslintrc — how do I migrate to flat config?

As of 2026-03-18, ESLint 10 (released February 6, 2026) has fully removed all legacy eslintrc support: ESLINT_USE_FLAT_CONFIG=false no longer works, .eslintrc.* and .eslintignore files are silently ignored, and LegacyESLint is gone. ESLint 9 had already made flat config (eslint.config.js) the mandatory default. Teams must choose a migration strategy: native flat config rewrite, automated migration with FlatCompat shims for unconverted shared configs, or @eslint/compat utilities for plugins whose rule context APIs have not been updated.

Native rewrite if all your plugins support flat config. FlatCompat shim if they don't yet. ESLint 10 removed the fallback entirely — migrate now.

Blockers

Who this is for

Candidates

Native Flat Config Rewrite (eslint.config.js)

Write eslint.config.js (or .mjs, .cjs, .ts) from scratch using the flat config array format. As of ESLint 10 (February 6, 2026), this is the only supported configuration path — no eslintrc fallback or ESLINT_USE_FLAT_CONFIG escape hatch exists. Use defineConfig() from eslint/config for type safety, automatic array flattening, and an extends array that accepts objects, arrays, or plugin config strings. Plugins become explicit JS imports; environments are replaced by the globals package; .eslintignore becomes an ignores property or the globalIgnores() helper.

When to choose

Best for small-team or monorepo projects where all plugins and shared configs already expose a flat config export (look for a flat/ prefix in the plugin's configs object or a configs['recommended'] that is not in eslintrc format). The single most decisive factor is whether every dependency in your extends chain ships a flat-config-compatible export — if yes, a native rewrite has zero ongoing shim overhead.

Tradeoffs

Pros: no compatibility shim overhead, full type inference via defineConfig(), unlocks ESLint 10's new config lookup semantics (searches from each linted file upward rather than from cwd), cleanest long-term solution. Cons: requires manually replacing every string-based plugin reference with a JS import and every env flag with a globals import; .eslintrc.js files lose all dynamic logic when converting because the migrate-config tool emits only evaluated static data for JS source configs.

Cautions

The npx @eslint/migrate-config tool works well for JSON and YAML source configs but explicitly does not preserve functions, conditionals, or programmatic logic in .eslintrc.js files - review generated output carefully before treating it as final. ESLint 10 also changed config file lookup to start from the directory of each linted file and walk toward the filesystem root, which can cause previously ignored directories to be linted if your ignores patterns relied on cwd-relative assumptions.

Automated Migration + FlatCompat Shim (@eslint/eslintrc)

Run npx @eslint/migrate-config <file> to auto-generate a eslint.config.js starting point, then wrap any shared configs that have not yet published a flat config export using FlatCompat from @eslint/eslintrc (e.g. compat.extends('eslint-config-my-legacy-config')). This bridges the gap when third-party shareable configs still export only eslintrc-format objects. FlatCompat translates extends, plugins, env, and overrides from eslintrc objects into the equivalent flat config array entries.

When to choose

Best for enterprise or monorepo teams whose dependency tree includes shared ESLint configs that have not migrated, where forking or replacing those configs is not feasible in the current sprint. Use FlatCompat as a temporary shim and track upstream migration status — remove it as soon as the shared config publishes a flat config export.

Tradeoffs

Pros: unblocks migration immediately without waiting for all upstream dependencies to update; migrate-config automates the mechanical parts of the rewrite for JSON/YAML source files; FlatCompat supports the full eslintrc surface including extends, plugins, env, globals, and overrides. Cons: FlatCompat cannot always expose individual plugin objects wrapped inside a legacy config (fixupConfigRules may be needed on top); adds @eslint/eslintrc as a runtime dev dependency; migrate-config output for .eslintrc.js files loses dynamic config logic and must be manually reconstructed.

Cautions

FlatCompat wraps the legacy config but some plugins inside it may still call deprecated context methods (context.getScope(), context.getFilename(), etc.) that were removed in ESLint 10 - those will throw at lint time and require fixupConfigRules or fixupPluginRules from @eslint/compat on top of FlatCompat. Do not ship FlatCompat to production indefinitely: it is an explicitly unsupported bridge and may not track future ESLint API changes.

Plugin Compat Utilities (fixupPluginRules / fixupConfigRules from @eslint/compat)

Install @eslint/compat and wrap individual plugins with fixupPluginRules() or wrap entire config arrays with fixupConfigRules() to proxy removed context methods (getScope, getFilename, getSourceCode, etc.) back to their new equivalents. These utilities specifically fix plugins that have not been updated to ESLint 10's revised rule context API, preventing runtime errors like 'context.getScope is not a function'. They are composable with both native flat configs and FlatCompat-wrapped legacy configs.

When to choose

Best for small-team or monorepo situations where a specific plugin you depend on throws context API errors under ESLint 10 but has not yet shipped a fix, and you cannot wait for the upstream release. Use fixupPluginRules for a single plugin import and fixupConfigRules when the broken plugin is embedded inside an imported config object or FlatCompat output.

Tradeoffs

Pros: surgical fix that targets only the affected plugin without touching the rest of the config; fixupConfigRules handles plugins nested inside config arrays without needing to identify them individually; minimal API surface. Cons: adds a proxy layer over every rule call in the wrapped plugin, which may mask true API mismatches; does not fix plugins that rely on removed Linter-level APIs (defineRule, defineParser) since those were removed at the ESLint class level in v10.

Cautions

fixupPluginRules and fixupConfigRules do not fix every possible legacy API use — they only proxy the specific deprecated context methods listed in the @eslint/compat documentation; plugins that use Linter.defineRule(), Linter.getRules(), or FileEnumerator will still fail because those were removed from the ESLint class entirely in v10. Always check the plugin's GitHub issues or changelog before assuming fixup utilities are sufficient.

Facts updated: 2026-03-18
Published: 2026-03-29

Try with your AI agent

$ npm install -g pocketlantern
$ pocketlantern init
# Restart Claude Code, Cursor, or your MCP client, then ask:
# "ESLint 9 killed .eslintrc — how do I migrate to flat config?"
Missing something? Request coverage