ESLint 07 🛡️ The files Key — Scoping rules to specific file patterns
In the flat config, every configuration object can carry a files key. The key accepts an array of glob patterns, and the object applies only to the files that match at least one of those patterns. An object without a files key applies to every file that ESLint processes. This is the mechanism that replaces the overrides array from the legacy .eslintrc format, and it is the primary tool for giving different files different rules within a single configuration file.
This chapter covers the files key syntax, the default behavior when it is omitted, the interaction with ignores inside the same object, the ordering rule that determines which object wins when multiple objects match the same file, and the mistake of applying a plugin’s recommended config without scoping it to the files it was meant for.
Key point: A configuration object without a files key applies to every file. A plugin’s recommended config is usually an object without a files key, which means it applies to every file unless the config is spread into an object that does have one. This is the most common cause of a TypeScript-only rule firing on JavaScript files.
Why the files key exists
The single-language problem. A project with only JavaScript files can apply one rule set to everything. A project with JavaScript, TypeScript, JSX, and test files cannot. A rule like @typescript-eslint/no-explicit-any is meaningless for a .js file, and a rule like no-console may be appropriate for source files but not for a CLI script. The files key is the mechanism for expressing “this object applies to these files and no others.”
The override replacement. The legacy .eslintrc format used an overrides array, where each entry had its own files pattern and its own nested configuration. The flat config flattens this: every configuration object is an override, and the files key scopes it. The ESLint migration guide describes the flat config array as “basically all configurations in the flat config file are like the overrides property of eslintrc” . The difference is that there is no nesting; the objects are siblings in an array, and the order determines precedence.
The extends interaction. The documentation recommends always using a files key when using the extends key, because omitting it causes the extended configuration to apply to all files . A shareable config or a plugin’s recommended config often has no files key of its own. When it is placed in the array directly, it applies globally. When it is spread into an object that has a files key, it is scoped to that pattern.
a. The files key syntax
The files key accepts a single glob pattern or an array of patterns. The patterns use minimatch syntax and are matched against the file path relative to the configuration file’s directory.
// eslint.config.js
export default [
{
files: ['**/*.test.js', '**/*.spec.js'],
rules: {
'no-console': 'off',
},
},
]
This object applies only to files ending in .test.js or .spec.js. Every other file is unaffected by the no-console setting in this object. If another object in the array sets no-console for all files, the test files receive the value from whichever object is later in the array.
A single object can combine files with a plugin’s recommended config by spreading the config into the object:
import tseslint from 'typescript-eslint'
export default [
{
files: ['**/*.ts', '**/*.tsx'],
...tseslint.configs.recommended,
},
]
The spread applies the recommended rules only to the TypeScript files. The Stack Overflow answer notes that without the files key, the recommended config objects apply to all configured files by default, which is not the intent .
b. The default when files is omitted
A configuration object without a files key applies to every file. This is the behavior that makes the array order matter, and it is the source of the most common scoping mistake.
export default [
{
rules: { 'no-console': 'error' }, // applies to all files
},
{
files: ['**/*.test.js'],
rules: { 'no-console': 'off' }, // overrides for test files
},
]
The first object sets no-console to error for every file. The second object sets it to off for test files. Because the second object is later in the array, it wins for the files that match its files pattern. The test files get off; everything else gets error.
The default behavior is also why a plugin’s recommended config placed directly in the array applies globally. The js.configs.recommended object from @eslint/js has no files key, so it applies to every file. That is the intended behavior for a base config that should apply everywhere. A TypeScript-specific config does not have the same intent, which is why it must be scoped.
c. Combining files and ignores in one object
An object can have both a files key and an ignores key. The files key selects the candidate files, and the ignores key removes files from that set. The two keys work together to define a precise scope.
export default [
{
files: ['**/*.js'],
ignores: ['**/*.config.js'],
rules: {
'no-console': 'error',
},
},
]
This object applies no-console to every .js file except the ones that match **/*.config.js. The ignores key inside an object with a files key is not a global ignore; it only filters the files selected by that object.
A global ignore is expressed differently. An object with only an ignores key and no other keys is a global ignore. It removes the matching files from ESLint’s processing entirely, not just from one configuration object.
export default [
{
ignores: ['dist/', 'coverage/'],
},
// ... other config objects
]
The ESLint ignore documentation states that an ignores key without any other keys acts as a global ignore . The patterns are added after the default ignores (**/node_modules/, .git/).
Complete Example Session
// ============================================
// PART 1: A SCOPED OBJECT
// ============================================
export default [
{
files: ['**/*.test.js'],
rules: {
'no-console': 'off',
},
},
]
// Applies only to test files.
// Other files are unaffected.
// ============================================
// PART 2: AN UNSCOPED OBJECT
// ============================================
export default [
{
rules: {
'no-console': 'error',
},
},
]
// Applies to every file.
// No files key means no restriction.
// ============================================
// PART 3: THE ORDER MATTERS
// ============================================
export default [
{ rules: { 'no-console': 'error' } }, // all files
{ files: ['**/*.test.js'], rules: { 'no-console': 'off' } }, // test files
]
// Test files get "off" because the second object is later.
// ============================================
// PART 4: SCOPING A PLUGIN CONFIG
// ============================================
import tseslint from 'typescript-eslint'
export default [
{
files: ['**/*.ts', '**/*.tsx'],
...tseslint.configs.recommended,
},
]
// The recommended config applies only to TypeScript files.
// ============================================
// PART 5: THE MISTAKE
// ============================================
import tseslint from 'typescript-eslint'
export default [
...tseslint.configs.recommended, // no files key
]
// The TypeScript rules apply to every file,
// including .js files where they are meaningless.
// ============================================
// PART 6: FILES AND IGNORES
// ============================================
export default [
{
files: ['**/*.js'],
ignores: ['**/*.config.js'],
rules: { 'no-console': 'error' },
},
]
// Applies to .js files except .config.js files.
// ============================================
// PART 7: GLOBAL IGNORES
// ============================================
export default [
{
ignores: ['dist/', 'coverage/'],
},
]
// An object with only ignores is a global ignore.
// ESLint does not process these files at all.
// ============================================
// PART 8: MULTIPLE PATTERNS
// ============================================
export default [
{
files: ['src/**/*.js', 'test/**/*.js'],
rules: { semi: ['error', 'always'] },
},
]
// The object applies to files in both directories.
// ============================================
// PART 9: THE EXTENDS RECOMMENDATION
// ============================================
// When using extends, always include a files key.
// Otherwise the extended config applies to all files.
// ============================================
// PART 10: THE COMPLETE CONFIG
// ============================================
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
export default [
{ ignores: ['dist/'] },
js.configs.recommended,
{
files: ['**/*.ts', '**/*.tsx'],
...tseslint.configs.recommended,
},
{
files: ['**/*.test.js'],
rules: { 'no-console': 'off' },
},
]
The ten parts covered a scoped object, an unscoped object, the order rule, scoping a plugin config, the unscoped mistake, files with ignores, global ignores, multiple patterns, the extends recommendation, and the complete config.
Quick Reference
The files key
| Property | Behavior |
|---|---|
files: ['**/*.ts'] | Applies only to matching files |
files: [...] omitted | Applies to every file |
files + ignores | Selects files, then filters them |
ignores alone | Global ignore |
Common patterns
| Pattern | Matches |
|---|---|
**/*.js | All JavaScript files |
**/*.test.js | Test files |
src/**/*.ts | TypeScript files in src/ |
*.config.js | Config files at the root |
The extends rule
| Scenario | files key | Result |
|---|---|---|
| Plugin config directly in array | No | Applies to all files |
| Plugin config spread into scoped object | Yes | Applies to matching files |
Global vs scoped ignores
| Key combination | Effect |
|---|---|
ignores only | Global ignore |
files + ignores | Scoped ignore |
Best Practices
✅ Do This:
// Scope plugin configs to the files they apply to
{ files: ['**/*.ts'], ...tseslint.configs.recommended } // ✅
// Put the base config first, then the overrides
[
js.configs.recommended,
{ files: ['**/*.test.js'], rules: { 'no-console': 'off' } },
] // ✅
// Use a global ignore for build output
{ ignores: ['dist/'] } // ✅
❌ Don’t Do This:
// Place a TypeScript config in the array unscoped
...tseslint.configs.recommended // ❌
// Rely on array order without understanding it
// The last matching object wins // ❌
// Use ignores alone when a scoped ignore is intended
{ ignores: ['**/*.test.js'] } // global, not scoped // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| TypeScript rules fire on JS files | Plugin config placed unscoped | Spread into an object with files |
| Test rules do not apply | Scoped object placed before the base object | Move the scoped object later in the array |
| Build output is linted | No global ignore | Add an object with only ignores |
| Scoped ignore removes files globally | ignores used without files | Combine files and ignores in one object |
| Rule applies to files it should not | No files key on the object | Add the files pattern |
Real-World Examples
1. Scope to TypeScript
{ files: ['**/*.ts'], rules: { ... } }
2. Scope to tests
{ files: ['**/*.test.js'], rules: { 'no-console': 'off' } }
3. Scope with an exclusion
{ files: ['**/*.js'], ignores: ['**/*.config.js'] }
4. Global ignore
{ ignores: ['dist/', 'coverage/'] }
5. Scoped plugin config
{ files: ['**/*.ts'], ...tseslint.configs.recommended }
6. Multiple patterns
{ files: ['src/**/*.js', 'test/**/*.js'] }
7. Base config first
[js.configs.recommended, { files: ['**/*.test.js'] }]
8. The unscoped mistake
[...tseslint.configs.recommended]
9. The extends recommendation
{ files: ['**/*.ts'], extends: [...] }
10. Complete structure
[{ ignores: ['dist/'] }, js.configs.recommended, { files: ['**/*.ts'], ...tseslint.configs.recommended }]
Visual
The scoping decision
┌─────────────────────────────────────────────────────────────┐
│ OBJECT WITH files │
│ { files: ['**/*.ts'], rules: { ... } } │
│ │ │
│ ▼ │
│ Applies only to .ts files │
│ │
│ OBJECT WITHOUT files │
│ { rules: { ... } } │
│ │ │
│ ▼ │
│ Applies to every file │
└─────────────────────────────────────────────────────────────┘
Array order
┌─────────────────────────────────────────────────────────────┐
│ Object 1: all files, no-console: error │
│ Object 2: test files, no-console: off │
│ │
│ test.js: Object 1 sets error, Object 2 sets off │
│ → Object 2 wins because it is later │
│ │
│ app.js: Object 1 sets error, Object 2 does not match │
│ → error │
└─────────────────────────────────────────────────────────────┘
Scoped vs global ignore
┌─────────────────────────────────────────────────────────────┐
│ { ignores: ['dist/'] } │
│ → global ignore, file not processed at all │
│ │
│ { files: ['**/*.js'], ignores: ['**/*.config.js'] } │
│ → scoped ignore, config files are excluded from this │
│ object but still processed by other objects │
└─────────────────────────────────────────────────────────────┘
The plugin config mistake
┌─────────────────────────────────────────────────────────────┐
│ WRONG: ...tseslint.configs.recommended │
│ → TypeScript rules apply to .js files │
│ │
│ RIGHT: { files: ['**/*.ts'], ...tseslint.configs.recommended }│
│ → TypeScript rules apply only to .ts files │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Key | files |
| Value | Glob pattern or array |
Without files | Applies to every file |
With files | Applies only to matching files |
files + ignores | Scoped ignore |
ignores alone | Global ignore |
| Precedence | Last matching object wins |
| Plugin configs | Must be scoped with files |
| Extends rule | Always include files |
Key takeaways:
- The
fileskey scopes a configuration object to a set of file patterns. An object withfiles: ['**/*.ts']applies only to TypeScript files. An object without afileskey applies to every file, which is the default and the source of most scoping mistakes . - Array order determines which object wins. When two objects match the same file, the later object overrides the earlier one. The base config goes first, and the file-specific overrides go after it.
- Plugin recommended configs are usually unscoped. Placing
...tseslint.configs.recommendeddirectly in the array applies the TypeScript rules to every file, including JavaScript files. Spreading the config into an object with afileskey scopes it correctly . filesandignorescan be combined in one object. Thefileskey selects the candidates, and theignoreskey filters them. This is a scoped ignore, not a global one.- An object with only
ignoresis a global ignore. It removes the matching files from ESLint’s processing entirely. The patterns are added after the default ignores fornode_modulesand.git. - The
extendskey requires afileskey. The ESLint documentation recommends always usingfileswithextends, because omitting it causes the extended configuration to apply to all files .
Remember: The files key is the scoping mechanism in the flat config. It replaces the overrides array from the legacy format. Without it, an object applies to everything. With it, the object applies only to the matching files. The array order decides precedence, so the base config goes first and the specific overrides go later. The most common mistake is placing a plugin’s recommended config unscoped, which applies TypeScript rules to JavaScript files and produces a wall of meaningless errors.
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!