ESLint 10.4.0 on May 15 Fixes Flat Config Ignore Drift: How to Use includeIgnoreFile() Right Now
ESLint 10.4.0 is a small release with one change that matters more than it looks: `includeIgnoreFile()` is now built into `eslint/config`, which makes flat config ignores much less annoying to manage.
On May 15, 2026, ESLint 10.4.0 shipped. It is not a flashy release. There is no giant migration story, no dramatic benchmark chart, no “rewrite your whole setup this weekend” energy.
Good. Those releases are exhausting.
The part worth stealing right now is includeIgnoreFile(), which ESLint now exports from eslint/config. If you are on flat config and you have ever duplicated .gitignore rules by hand, watched them drift, and then wondered why dist/ or generated files suddenly came back into CI lint runs, this release is for you.
Why this tiny feature matters
Flat config cleaned up a lot of ESLint weirdness, but ignore handling has stayed more annoying than it should be. Teams usually land in one of these buckets:
They copy
.gitignorepatterns intoglobalIgnores()and forget about them.They keep a second ignore list just for ESLint and hope nobody notices the duplication.
They lint too much junk and blame ESLint for being slow.
That last one is especially common in monorepos, codegen-heavy repos, or anything with build artifacts scattered across packages.
ESLint 10.4.0 fixes the obvious paper cut. According to the release notes and the updated ignore files docs, includeIgnoreFile() can now pull patterns from .gitignore files or other gitignore-style files directly into flat config. It also supports multiple files and can resolve patterns relative to each ignore file.
That last part is the real win. Nested ignore files were where the old approach got annoying fast.
The upgrade most teams should make this week
If your flat config still looks like this, you can probably do better:
import { defineConfig, globalIgnores } from "eslint/config";
export default defineConfig([
globalIgnores([
"dist/",
"coverage/",
"build/",
"*.min.js",
".next/",
]),
]);This works until it doesn’t. Somebody updates .gitignore. ESLint does not get the memo. Now your editor and your CI disagree about what counts as “real” source.
Here is the cleaner version in ESLint 10.4.0:
import { defineConfig, includeIgnoreFile } from "eslint/config";
import { fileURLToPath } from "node:url";
const gitignorePath = fileURLToPath(new URL(".gitignore", import.meta.url));
export default defineConfig([
includeIgnoreFile(gitignorePath, { gitignoreResolution: true }),
{
rules: {
// your rules here
},
},
]);That is the whole pitch. One source of truth. Less drift. Less config sludge.
The monorepo version is where it gets actually useful
The docs for v10.4.0 show something more interesting than the single-file case: includeIgnoreFile() accepts an array. If your repo has a root .gitignore plus package-level ignore files, you can import all of them.
import { defineConfig, includeIgnoreFile } from "eslint/config";
import { fileURLToPath } from "node:url";
const rootGitignore = fileURLToPath(new URL(".gitignore", import.meta.url));
const webGitignore = fileURLToPath(new URL("apps/web/.gitignore", import.meta.url));
const apiGitignore = fileURLToPath(new URL("apps/api/.gitignore", import.meta.url));
export default defineConfig([
...includeIgnoreFile([rootGitignore, webGitignore, apiGitignore], {
gitignoreResolution: true,
}),
]);If you have ever had package-local build output sneak into lint runs because glob resolution got weird, this is the part to care about.
I would not oversell it. This does not magically solve every monorepo linting problem. But it does remove one of the dumbest ones.
One gotcha you can absolutely trip over
Use gitignoreResolution: true when you are importing actual .gitignore files.
That option is not decorative. The docs are pretty explicit here: .gitignore patterns should be interpreted relative to the location of the .gitignore file itself. If you skip that option, ESLint resolves patterns relative to the config file instead.
That means a nested ignore file can look correct and still behave wrong.
This is the kind of bug that burns half an afternoon because nothing looks broken at first glance. You just keep seeing generated files show up where they should not.
Where globalIgnores() still makes sense
This release does not kill globalIgnores(). It just means you should stop using it for patterns that already live somewhere else.
I would keep the split simple:
Use
includeIgnoreFile()for.gitignoreand other shared ignore files.Use
globalIgnores()for ESLint-only exclusions that should not affect Git.
That usually means something like this:
import {
defineConfig,
globalIgnores,
includeIgnoreFile,
} from "eslint/config";
import { fileURLToPath } from "node:url";
const gitignorePath = fileURLToPath(new URL(".gitignore", import.meta.url));
export default defineConfig([
includeIgnoreFile(gitignorePath, { gitignoreResolution: true }),
globalIgnores(["eslint-report.json"]),
]);That division is easier to explain to the next person who inherits the repo, which matters more than most people admit.
What about the other ESLint 10.4.0 feature?
There is one other feature in 10.4.0: the for-direction rule now checks sequence expressions. Nice fix. Real rule work. I am glad it shipped.
Still, includeIgnoreFile() is the thing I would upgrade for first. It removes duplicated config, makes flat config less fiddly, and helps large repos stay sane without inventing another local convention.
That is a better kind of tooling release. Small surface area. Immediate payoff.
Should you upgrade now?
If your team is already on ESLint 10 and flat config, yes. This is an easy upgrade with a very normal reward: fewer confusing lint targets.
If you are still carrying @eslint/compat mainly to bridge ignore-file behavior, this release is also a nudge to simplify. The official release post notes that includeIgnoreFile() was previously available there, and now the core eslint/config entrypoint has the version most teams actually want.
So no, ESLint 10.4.0 is not a big dramatic release. I do not think it needs to be. Sometimes the best tooling update is the one that quietly deletes 20 lines of config and stops wasting your time.
That is exactly what this one does.