ESLint v9 Flat Config: Replace .eslintrc Without Breaking Your Lint
ESLint v9 ignores .eslintrc when eslint.config.js exists. Here's how to write flat config, verify per-file rules, and avoid silent rule loss.
19 Sept 2026, 00:38 UTC

Your lint rules stopped working after upgrading to ESLint v9
You run npx eslint . and nothing complains about unused variables or missing semicolons. The command exits cleanly even though your code clearly violates the rules you remember from .eslintrc. This is not a bug. ESLint v9 uses flat config by default. If an eslint.config.js file exists, ESLint ignores .eslintrc entirely. If you have not written the new config file, your legacy rules are silently disabled.
The useful takeaway: flat config is just a JavaScript module that exports an array of configuration objects. You import plugins and globals directly instead of referencing them by string, and you can inspect exactly which rules apply to any file before you commit.
What changed in the configuration format
Legacy ESLint config used .eslintrc.json, .eslintrc.yml, or .eslintrc.js with keys like env, parserOptions, and globals. Flat config replaces all of that with eslint.config.js (or eslint.config.mjs). Each object in the exported array can target specific files with files, exclude paths with ignores, and define language options, plugins, and rules directly.
Because the config file is JavaScript, ESLint v9 treats it as native ESM. Your project needs Node.js 18 or later, and the file must use import and export default unless your package.json sets \"type\": \"commonjs\" and you use .cjs.
Worked example: source, tests, and generated code
This minimal config applies strict rules to TypeScript source, relaxes them for test files, and ignores build output. Run this command in your project root:
npx eslint --version
Confirm the output starts with 9.. If you see 8., flat config is not the default and this guide does not apply.
Create eslint.config.js in the project root. No special permissions are required beyond write access to the repository.
import eslint from '@eslint/js';
import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';
import globals from 'globals';
export default [
eslint.configs.recommended,
{
files: ['**/*.ts'],
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
globals: {
...globals.browser,
...globals.node,
},
},
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
},
},
{
files: ['**/*.test.ts', '**/*.spec.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
},
},
{
ignores: ['dist/', 'coverage/', 'build/'],
},
];
Key differences from legacy config:
languageOptions.parserreplaces the top-levelparserstring.globals.browserandglobals.nodecome from theglobalspackage, notenv.- Plugin rules are referenced as
'@typescript-eslint/no-explicit-any', not with aplugin:prefix. - Multiple config objects are merged in order, so later objects can override earlier ones for matching files.
Verify which config applies to a file
Do not guess whether your new object is catching the right files. Run this diagnostic in the project root:
npx eslint --print-config src/components/Button.ts
Replace src/components/Button.ts with a real path in your project. The command prints a large JSON object. Inspect the rules key to confirm that @typescript-eslint/no-explicit-any is set to \"warn\" for that file. If the rule is missing or set to \"off\", your files glob or object order is likely wrong.
Risk: --print-config shows resolved configuration, not lint results. A clean config dump does not mean your code passes lint.
Trade-offs and limitations
- No automatic migration. ESLint does not convert
.eslintrcfor you. You must rewrite the config manually or use a community migration tool. - Silent disabling. If both
.eslintrcandeslint.config.jsexist in v9, the legacy file is ignored. You may think rules are active when they are not. - ESM only by default. Teams locked to CommonJS may need
eslint.config.cjsor adjustments topackage.json. - Plugin API changes. Some older plugins have not released flat-config-compatible exports. Check the plugin documentation before adopting v9.
Practical checklist before you commit
- Run
npx eslint --versionand confirm major version 9. - Write
eslint.config.jsand keep your old.eslintrctemporarily for comparison. - Run
npx eslint .in the project root. Expect lint errors from your rules; config load errors indicate a syntax or import problem. - Run
npx eslint --print-configon one source file, one test file, and one generated file to confirm each receives the intended rules. - If you need to revert, delete or rename
eslint.config.js. In ESLint v9,.eslintrcwill still be ignored, so a true rollback to legacy behavior requires downgrading to ESLint v8.
Flat config moves ESLint configuration from declarative files to executable JavaScript. That makes it more flexible, but it also means a typo in your config file can disable your entire lint pipeline. Verify per-file resolution with --print-config before you assume your rules are active.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.