| | |

ESLint 08 🛡️ Global Ignores — The ignores property in flat config

⏱️ Estimated read: 11 min | |

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 shapeBehavior
{ ignores: [...] } onlyGlobal ignore, file not processed
{ files: [...], ignores: [...] }Local ignore, filters the object’s scope
globalIgnores([...])Explicit global ignore

Default ignores

PatternSource
**/node_modules/Default
.git/Default
Custom patternsAdded after the defaults

Pattern rules

PatternMatches
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.jsNegates the previous ignore

The negation rule

ScenarioWorks
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

PitfallWhy It HappensFix
Test files are not linted at allGlobal ignore used instead of localUse files + ignores in the same object
Negation has no effectParent directory is excluded with /**Use /**/* and unignore the file
.eslintignore is ignoredFile is not supported in flat configMove the patterns into ignores
Dotfiles are linted unexpectedlyFlat config does not ignore them by defaultAdd **/.* to the ignore patterns
Ignore pattern does not match nested directoriesMissing **/ prefixUse **/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

ItemValue
Global ignoreObject with only ignores
Local ignoreObject with ignores and other keys
HelperglobalIgnores() from eslint/config
Default ignores**/node_modules/, .git/
DotfilesLinted by default, ignore with **/.*
Negation!pattern, but parent directory must be unignored
Recursive directory**/directory/
.eslintignoreNot supported

Key takeaways:

  • An object with only ignores is a global ignore. The matching files are not processed by ESLint at all. An object with ignores and 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 from eslint/config and from @eslint/config-helpers for 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 .
  • .eslintignore is not supported. The patterns must be moved into the ignores property in eslint.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 .eslintignore behavior 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 be directory/**/*, 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 /**.

Was this helpful?


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!