Skip to content

Migrating to ESLint Flat Config: It's More Than Just a Filename Change

ESLint Flat Config is now the standard.

3 min read
ESLint
Flat Config
Monorepo
Linting
Migration

ESLint Flat Config is now the standard. While the old .eslintrc format still works, almost all the recent documentation, plugins, and online references have moved to eslint.config.js. Staying on the old format only makes life harder when you're trying to keep your tooling up to date.

I finally migrated the linting packages in my monorepo once the flat config system proved itself to be stable. My main goal was to avoid bugs caused by extends resolution and to build a configuration structure that actually aligns with how ESLint works internally.

The shift in logic is pretty significant. We used to rely on a "cascade" system via extends or overrides. In the new system, everything is just an array of configuration objects. Order is everything here; the last object in the array wins if there are conflicting rules.

This change in mindset is vital, especially when handling ignores. In the old system, we used ignorePatterns. In the new one, if you put ignores inside the same object as files, it only applies to that specific block. To make ignores a global rule, it has to sit in its own separate object. I actually messed this up once, which resulted in my dist/ folder still getting linted even though I thought I had it covered.

To speed up the migration, I took a few specific steps. First, I mapped out every single extends and plugin. In the new system, you can't just call plugin:react/recommended and hope for the best; you have to import the actual configuration object directly.

Second, I didn't just delete the old config files immediately. I kept eslint.config.js alongside the old files and made sure our entire CI pipeline was running smoothly before finally ditching .eslintrc. Leaving two different sources of truth for too long just leads to inconsistent rules.

I also restructured our shared config packages. Instead of exporting a single object, I now export an array so users can just spread it into their own configs easily. For TypeScript integration, I used functions from typescript-eslint to keep things tidy.

There are a few things to watch out for, like the loss of the root: true concept, which was used to stop ESLint from searching parent directories. In a monorepo environment, losing this actually makes more sense because it allows every package to manage its own configuration independently.

Plugin compatibility was another hurdle. Not every plugin supports flat config out of the box. Some provide ready-to-use arrays, but others are still stuck in the old format. If you see errors about "unknown keys," it's usually a sign you're trying to force an old structure into the new system. The fix is to correct your import method, not just silence the error.

Also, if your ignore rules aren't managed correctly, you'll get flooded with errors from files you never intended to lint in the first place.

This migration definitely requires an upfront time investment, but it's much better than clinging to outdated configurations. Good luck with your migration.