ESLint 08 🛡️ Global Ignores — The ignores property in flat config
In the flat config, file ignoring is not controlled by a separate .eslintignore file. The .eslintignore file is no longer supported . Instead, ignore patterns are expressed through the ignores property inside eslint.config.js. The behavior of that property depends on what else is in the same configuration object. An object with only an ignores key acts as a global ignore: ESLint does not process the matching files at all. An object that has an ignores key alongside files, rules, or any other configuration acts as a local ignore: it filters the files that the object applies to, but the files are still processed by other configuration objects in the array .
This chapter covers the global ignore, the local ignore, the globalIgnores() helper that makes the distinction explicit, the pattern rules that differ from .gitignore, and the negation behavior that determines whether a file can be re-included inside an ignored directory.
Key point: An object with only ignores is a global ignore. An object with ignores and anything else is a local ignore. The globalIgnores() helper from eslint/config makes the intent explicit and is the recommended way to write global ignores in current ESLint versions .
Why the distinction matters
The .eslintignore removal problem. The legacy .eslintignore file was a single-purpose file: every pattern in it was a global ignore. The flat config removed that file, and the ignores key inherited both the global-ignore role and the local-ignore role. The same key now means different things depending on context, which is the source of the confusion that the ESLint team addressed with the globalIgnores() helper .
The processing difference. A globally ignored file is not linted at all. If the file is passed explicitly on the command line, ESLint reports a warning unless --no-warn-ignored is set . A locally ignored file is still linted by other configuration objects that match it; the local ignores only removes it from the scope of one specific object.
The ordering problem. A global ignore object should be placed at the beginning of the configuration array. If it is placed after other objects, the behavior is still global, but placing it first makes the intent clear and prevents a future reader from mistaking it for a local ignore .
a. The global ignore
An object with only an ignores key is a global ignore. The patterns are added to the default ignores, which are **/node_modules/ and .git/ .
// eslint.config.js
export default [
{
ignores: ['dist/', 'coverage/', 'build/'],
},
// ... other config objects
]
A file that matches dist/ is not processed by ESLint. It does not appear in the output, and no rules are evaluated against it. The pattern dist/ matches the directory at any depth; to match only the root-level dist, the pattern is /dist/ .
The globalIgnores() helper achieves the same result with an explicit name.
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist/', 'coverage/', 'build/']),
// ... other config objects
])
The helper is available from eslint/config in ESLint 9.x and later, and from @eslint/config-helpers for older versions that support flat config . It is a thin wrapper, but it removes the ambiguity: a reader sees globalIgnores and knows the files are not processed, not merely filtered.
b. The local ignore
An object with an ignores key and at least one other key is a local ignore. It applies to the files selected by the object’s files key (or to all files if files is absent), minus the files that match ignores.
export default [
{
files: ['**/*.js'],
ignores: ['**/*.config.js'],
rules: {
'no-console': 'error',
},
},
]
This object applies no-console to every .js file except the ones matching **/*.config.js. The config files are not globally ignored; they are still processed by any other configuration object that matches them. The local ignores only excludes them from this specific object’s rule .
A common pattern is to ignore test files for a production-only rule while keeping the test files linted by the rest of the configuration.
export default [
{
files: ['src/**/*.js'],
ignores: ['src/**/*.test.js'],
rules: { 'no-console': 'error' },
},
{
files: ['src/**/*.test.js'],
rules: { 'no-console': 'off' },
},
]
The first object applies the strict rule to source files but not to test files. The second object applies the relaxed rule to test files. The test files are not globally ignored; they are linted with a different rule set.
c. Pattern rules and negation
The ignores patterns follow minimatch syntax, but they differ from .gitignore in two important ways.
Dotfiles are not ignored by default. In flat config, dotfiles such as .dotfile.js are linted unless an explicit pattern excludes them. The .eslintignore file previously ignored them; the flat config does not. To ignore dotfiles, the pattern **/.* must be added .
The parent-directory rule for negation. A negated pattern (!pattern) can re-include a file that was previously ignored, but it cannot re-include a file if its parent directory is excluded. The directory must be unignored first. The ESLint documentation describes the solution for ignoring a directory except for specific files: use directory/**/* instead of directory/**, because directory/** ignores the entire directory and traversal skips it, while directory/**/* ignores only its contents and allows a negation to re-include a file .
// Ignore everything in build/ except build/test.js
{
ignores: [
'build/**/*', // ignore all contents
'!build/test.js', // unignore this file
],
}
To ignore a directory at any depth, the pattern must start with **/. A pattern like .config only matches the .config directory next to the configuration file; **/.config/ matches every directory with that name .
Complete Example Session
// ============================================
// PART 1: THE GLOBAL IGNORE
// ============================================
export default [
{
ignores: ['dist/', 'coverage/'],
},
// ... other config objects
]
// dist/ and coverage/ are not processed.
// ============================================
// PART 2: THE globalIgnores HELPER
// ============================================
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist/', 'coverage/']),
])
// Explicit and unambiguous.
// ============================================
// PART 3: THE LOCAL IGNORE
// ============================================
export default [
{
files: ['**/*.js'],
ignores: ['**/*.config.js'],
rules: { 'no-console': 'error' },
},
]
// Config files are excluded from this object's rule,
// but they are still processed by other objects.
// ============================================
// PART 4: THE TEST FILE PATTERN
// ============================================
export default [
{
files: ['src/**/*.js'],
ignores: ['src/**/*.test.js'],
rules: { 'no-console': 'error' },
},
{
files: ['src/**/*.test.js'],
rules: { 'no-console': 'off' },
},
]
// Test files are linted with a relaxed rule.
// They are not globally ignored.
// ============================================
// PART 5: THE DEFAULT IGNORES
// ============================================
// ESLint ignores **/node_modules/ and .git/ by default.
// Custom ignores are added after the defaults.
// ============================================
// PART 6: DOTFILES
// ============================================
export default [
{
ignores: ['**/.*'],
},
]
// Dotfiles are linted by default in flat config.
// This pattern restores the old .eslintignore behavior.
// ============================================
// PART 7: THE NEGATION RULE
// ============================================
export default [
{
ignores: [
'build/**/*', // ignore all contents
'!build/test.js', // unignore this file
],
},
]
// build/test.js is linted.
// Other files in build/ are ignored.
// ============================================
// PART 8: THE PARENT DIRECTORY MISTAKE
// ============================================
export default [
{
ignores: [
'build/**', // ignores the directory entirely
'!build/test.js', // does nothing
],
},
]
// The parent directory is ignored.
// The negation cannot re-include a file inside it.
// ============================================
// PART 9: THE RECURSIVE DIRECTORY PATTERN
// ============================================
export default [
{
ignores: ['**/.config/'],
},
]
// Matches .config at any depth.
// ".config/" only matches the root-level directory.
// ============================================
// PART 10: THE COMPLETE CONFIG
// ============================================
import { defineConfig, globalIgnores } from 'eslint/config'
import js from '@eslint/js'
export default defineConfig([
globalIgnores(['dist/', 'coverage/']),
js.configs.recommended,
{
files: ['**/*.js'],
ignores: ['**/*.config.js'],
rules: { 'no-console': 'error' },
},
])
The ten parts covered the global ignore, the globalIgnores() helper, the local ignore, the test file pattern, the default ignores, dotfiles, the negation rule, the parent directory mistake, the recursive directory pattern, and the complete config.
Quick Reference
Global vs local ignores
| Object shape | Behavior |
|---|---|
{ ignores: [...] } only | Global ignore, file not processed |
{ files: [...], ignores: [...] } | Local ignore, filters the object’s scope |
globalIgnores([...]) | Explicit global ignore |
Default ignores
| Pattern | Source |
|---|---|
**/node_modules/ | Default |
.git/ | Default |
| Custom patterns | Added after the defaults |
Pattern rules
| Pattern | Matches |
|---|---|
dist/ | Directory at any depth |
/dist/ | Directory at the root only |
**/.config/ | .config at any depth |
**/.* | Dotfiles |
build/**/* | Contents of build/, not the directory |
!build/test.js | Negates the previous ignore |
The negation rule
| Scenario | Works |
|---|---|
Unignore a file inside an ignored directory with directory/**/* | Yes |
Unignore a file inside an ignored directory with directory/** | No |
Best Practices
✅ Do This:
// Use globalIgnores for project-wide ignores
globalIgnores(['dist/', 'coverage/']) // ✅
// Place global ignores at the beginning of the array
export default [globalIgnores(['dist/']), ...] // ✅
// Use directory/**/* for unignore patterns
ignores: ['build/**/*', '!build/test.js'] // ✅
// Ignore dotfiles explicitly if needed
ignores: ['**/.*'] // ✅
❌ Don’t Do This:
// Use an object with only ignores and expect a local scope
{ ignores: ['**/*.test.js'] } // global, not local // ❌
// Negate a file inside an ignored directory with directory/**
ignores: ['build/**', '!build/test.js'] // ❌
// Assume dotfiles are ignored by default
// Flat config lints them unless excluded // ❌
// Use .eslintignore
// It is not supported in flat config // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Test files are not linted at all | Global ignore used instead of local | Use files + ignores in the same object |
| Negation has no effect | Parent directory is excluded with /** | Use /**/* and unignore the file |
.eslintignore is ignored | File is not supported in flat config | Move the patterns into ignores |
| Dotfiles are linted unexpectedly | Flat config does not ignore them by default | Add **/.* to the ignore patterns |
| Ignore pattern does not match nested directories | Missing **/ prefix | Use **/directory/ for recursive matching |
Real-World Examples
1. Global ignore
{ ignores: ['dist/', 'coverage/'] }
2. globalIgnores helper
globalIgnores(['dist/', 'coverage/'])
3. Local ignore
{ files: ['**/*.js'], ignores: ['**/*.config.js'] }
4. Test file exclusion
{ files: ['src/**/*.js'], ignores: ['src/**/*.test.js'] }
5. Dotfile ignore
{ ignores: ['**/.*'] }
6. Negation
{ ignores: ['build/**/*', '!build/test.js'] }
7. Recursive directory
{ ignores: ['**/.config/'] }
8. Default ignores
**/node_modules/, .git/
9. The parent directory mistake
{ ignores: ['build/**', '!build/test.js'] } // ❌
10. The complete config
defineConfig([globalIgnores(['dist/']), js.configs.recommended])
Visual
The two meanings of ignores
┌─────────────────────────────────────────────────────────────┐
│ { ignores: ['dist/'] } │
│ → global ignore │
│ → dist/ is not processed at all │
│ │
│ { files: ['**/*.js'], ignores: ['**/*.config.js'] } │
│ → local ignore │
│ → config files are excluded from this object │
│ → but still processed by other objects │
└─────────────────────────────────────────────────────────────┘
The globalIgnores helper
┌─────────────────────────────────────────────────────────────┐
│ globalIgnores(['dist/', 'coverage/']) │
│ │
│ Explicit. Unambiguous. │
│ The reader knows the files are not processed. │
└─────────────────────────────────────────────────────────────┘
The negation parent rule
┌─────────────────────────────────────────────────────────────┐
│ build/** → ignores the directory entirely │
│ traversal skips it │
│ negation cannot re-include │
│ │
│ build/**/* → ignores only the contents │
│ the directory is traversed │
│ negation can re-include build/test.js │
└─────────────────────────────────────────────────────────────┘
Dotfiles
┌─────────────────────────────────────────────────────────────┐
│ Flat config: dotfiles are linted by default. │
│ .eslintignore: dotfiles were ignored. │
│ │
│ To ignore: ignores: ['**/.*'] │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Global ignore | Object with only ignores |
| Local ignore | Object with ignores and other keys |
| Helper | globalIgnores() from eslint/config |
| Default ignores | **/node_modules/, .git/ |
| Dotfiles | Linted by default, ignore with **/.* |
| Negation | !pattern, but parent directory must be unignored |
| Recursive directory | **/directory/ |
.eslintignore | Not supported |
Key takeaways:
- An object with only
ignoresis a global ignore. The matching files are not processed by ESLint at all. An object withignoresand any other key is a local ignore: it filters the files that the object applies to, but the files are still processed by other objects . - The
globalIgnores()helper makes the intent explicit. It is the recommended way to write global ignores in current ESLint versions. It is available fromeslint/configand from@eslint/config-helpersfor older versions . - Global ignores should be placed first in the array. The placement does not change the behavior, but it makes the intent clear and prevents the object from being mistaken for a local ignore .
.eslintignoreis not supported. The patterns must be moved into theignoresproperty ineslint.config.js. There is no separate ignore file in the flat config .- Dotfiles are not ignored by default. The flat config lints them unless an explicit pattern excludes them. The
.eslintignorebehavior of ignoring dotfiles is not preserved . - The negation rule requires the parent directory to be unignored. A negated pattern cannot re-include a file inside a directory that is excluded with
directory/**. The pattern must bedirectory/**/*, which ignores the contents but not the directory itself, so traversal can reach the file .
Remember: In the flat config, ignores has two meanings. An object with only ignores is a global ignore: the files are not processed. An object with ignores and anything else is a local ignore: the files are excluded from that object’s scope but still processed elsewhere. Use globalIgnores() to make the global intent explicit. Use files and ignores together for local scoping. And remember that the negation rule is not the same as .gitignore: you cannot unignore a file inside a directory that is excluded with /**.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!