.eslintrc.json Reference and Migration to ESLint Flat Config (eslint.config.js)
Last updated:
.eslintrc.json is the legacy ESLint configuration file — a static JSON document that lists which rules, plugins, parsers, and shareable configs apply to a project. It works through inheritance: ESLint walks up from the file being linted, finds the nearest .eslintrc.json, and merges it with parent configs along the way. ESLint 9 (April 2024) made the newer flat config (eslint.config.js) the default and put eslintrc behind an opt-in flag; ESLint 10 (late 2025) removed legacy support from the core CLI entirely, leaving the @eslint/eslintrc backward-compatibility package as the bridge for projects that have not yet migrated. This page is both a reference for the legacy format and a migration guide to the modern one.
Editing an .eslintrc.json by hand and ESLint refuses to load it? Paste the file into Jsonic's JSON Validator — it catches the trailing commas, smart quotes, and stray comments that cause silent parse failures.
Is .eslintrc.json still supported? (ESLint 9, 10, and the deprecation timeline)
The short answer: it depends on which ESLint version you run. The eslintrc family of configs — .eslintrc.json, .eslintrc.js, .eslintrc.yml, and the eslintConfig field in package.json — was the only supported format from ESLint 1.0 through ESLint 8.x. ESLint 9 shipped in April 2024 and inverted the default: flat config (eslint.config.js) became the format ESLint looks for first, and eslintrc became opt-in behind the ESLINT_USE_FLAT_CONFIG=false environment variable.
ESLint 10, released in late 2025, removed the legacy code path from the core CLI entirely. Projects that still ship an .eslintrc.json on ESLint 10+ must either install the @eslint/eslintrc backward-compatibility package (which provides a FlatCompat helper that translates legacy configs into flat config objects at runtime) or migrate the config outright. The shim works, but it adds a dependency and a small startup cost on every lint run.
Plugin authors are following the same arc. Newer major releases of popular plugins (eslint-plugin-react, @typescript-eslint/*, eslint-plugin-import) ship flat-config-first exports and treat the old plugin:name/recommended string as a deprecated alias. If you pin to older plugin majors to keep .eslintrc.json working, you eventually fall behind on rule updates. The right move for active projects is to migrate; for archived projects, pin ESLint to 8.x and the plugins to their last eslintrc-supporting majors.
Anatomy of .eslintrc.json: root, extends, plugins, rules, overrides
A minimal .eslintrc.json is a JSON object with at least one of extends or rules. The smallest useful config looks like this:
{
"root": true,
"extends": ["eslint:recommended"]
}root: true stops ESLint from walking further up the directory tree looking for parent configs — important for monorepos and any project where you do not want a stray .eslintrc.json in your home directory leaking into the build. Without root: true, ESLint keeps climbing until it hits the filesystem root.
A real-world config layers in plugins, parser settings, environment globals, and per-file overrides:
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module",
"ecmaFeatures": { "jsx": true }
},
"env": { "browser": true, "node": true, "es2022": true },
"plugins": ["@typescript-eslint", "react", "react-hooks"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"settings": { "react": { "version": "detect" } },
"rules": {
"no-console": ["warn", { "allow": ["warn", "error"] }],
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
},
"overrides": [
{
"files": ["**/*.test.ts", "**/*.test.tsx"],
"env": { "jest": true },
"rules": { "@typescript-eslint/no-explicit-any": "off" }
}
],
"ignorePatterns": ["dist/", "build/", "coverage/", "*.config.js"]
}Each top-level field has a specific job and a specific merge behavior — covered in the sections that follow.
extends: shareable configs, plugin: prefix, and merge order
extends is an array of strings. Each string is either a shareable config package, a plugin-provided config, or a relative path to another config file. ESLint resolves each entry, loads the config it points at, and merges them in order — later entries override earlier ones, and your own top-level rules override everything in extends.
{
"extends": [
"eslint:recommended",
"airbnb-base",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"./configs/internal.json"
]
}There are three resolution patterns to remember:
- Built-in:
eslint:recommendedandeslint:allare special strings that load ESLint's bundled configs — no package install required. - Shareable config package:
airbnb-baseresolves to the npm packageeslint-config-airbnb-base. Theeslint-config-prefix is implicit. - Plugin config:
plugin:react/recommendedloads therecommendedconfig exported byeslint-plugin-react. The plugin must also appear in thepluginsarray.
Order matters because of the merge: put broad baselines first (eslint:recommended), then opinionated style guides (airbnb-base), then framework- and language-specific configs, then formatting-related configs (prettier) last so they can disable conflicting style rules. The Prettier config is conventionally last for exactly this reason — it turns off ESLint rules that would fight Prettier's formatter.
See our ESLint config patterns guide for a deeper look at composing multiple shareable configs without rule conflicts.
rules object: severity (off/warn/error/0/1/2) and rule options
rules is an object mapping rule names to either a severity or a two-element array of [severity, options]. The severity field accepts either strings (off, warn, error) or numbers (0, 1, 2) — they are interchangeable. Strings read better in JSON; numbers show up in older codebases and shareable configs.
{
"rules": {
"no-console": "warn",
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"prefer-const": ["error", { "destructuring": "all" }],
"complexity": ["error", { "max": 10 }],
"no-var": 2,
"semi": ["error", "never"],
"quotes": ["error", "single", { "avoidEscape": true }],
"indent": ["error", 2, { "SwitchCase": 1 }],
"react/no-unescaped-entities": "off"
}
}The severity map:
off/0— rule disabled; no message at all.warn/1— yellow squiggle, does not fail the lint run (exit code 0).error/2— red squiggle, fails the lint run (exit code 1).
Rule options are passed as the second array element. The shape depends on the rule — check the rule's documentation page on eslint.org/docs/latest/rules/<rule-name>. Most rules accept either a string preset ("always", "never") or an options object. Severity-only entries are sugar for [severity, ...defaultOptions].
CI tip: use --max-warnings 0 on the lint command to make warnings fail the build alongside errors. That keeps the warn/error distinction useful for local development without letting warnings accumulate over time.
overrides: pattern-based per-file overrides
overrides is an array of config objects scoped to file patterns. Each entry has a required files field (a glob or array of globs) plus any other config fields — rules, env, parser, parserOptions, extends. ESLint applies the top-level config first, then layers each matching overrides entry on top in array order.
{
"rules": { "no-console": "error" },
"overrides": [
{
"files": ["scripts/**/*.js", "*.config.js"],
"env": { "node": true },
"rules": { "no-console": "off" }
},
{
"files": ["**/*.ts", "**/*.tsx"],
"parser": "@typescript-eslint/parser",
"extends": ["plugin:@typescript-eslint/recommended"],
"rules": { "@typescript-eslint/no-explicit-any": "warn" }
},
{
"files": ["**/*.test.ts"],
"env": { "jest": true },
"rules": { "@typescript-eslint/no-non-null-assertion": "off" }
},
{
"files": ["**/*.d.ts"],
"rules": { "@typescript-eslint/no-unused-vars": "off" }
}
]
}The pattern matches against the file path relative to the config file's location. Globs use minimatch syntax: * matches one segment, ** matches any depth, {a,b} is alternation. Multiple overrides entries can match the same file — they layer in array order, with later entries winning.
overrides is the right tool whenever the same project needs different parser settings for different file types, different environments for tests vs source, or relaxed rules for generated/legacy code. Avoid scattering multiple .eslintrc.json files in subdirectories for the same effect — overrides keeps the whole config in one place.
env, parserOptions, globals, and ignorePatterns
These four fields configure what ESLint knows about the runtime and which files it touches.
env— declares predefined globals. Settingbrowser: truemakeswindowanddocumentknown;node: trueaddsprocessand__dirname;es2022: trueturns on parser features for that ECMAScript edition.parserOptions— tells the parser how to read source files.ecmaVersion(a year orlatest) sets the language level;sourceTypepicksmodule(ES modules) orscript;ecmaFeatures.jsx: trueenables JSX.globals— additional globals beyond whatenvprovides, mapped to"readonly"or"writable". Useful for project-specific globals injected by a bundler or a test framework.ignorePatterns— file globs that ESLint skips entirely. An alternative to the separate.eslintignorefile. Patterns use gitignore syntax.
{
"env": { "browser": true, "node": true, "es2022": true },
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module",
"ecmaFeatures": { "jsx": true }
},
"globals": {
"__APP_VERSION__": "readonly",
"ga": "readonly"
},
"ignorePatterns": [
"dist/",
"build/",
"coverage/",
"node_modules/",
"*.generated.ts",
"!*.config.ts"
]
}The leading ! on the last pattern is a negation — it re-includes files that an earlier pattern would have ignored. Useful when a broad ignore accidentally swallows a file you do want linted.
See package.json eslintConfig field if you prefer to keep the config inline instead of in a separate file.
Migrating .eslintrc.json to eslint.config.js (flat config)
The migration changes both the file format and the resolution model. Flat config is a JavaScript module that default-exports an array of config objects. Plugins and parsers are imported as real modules instead of being looked up from strings. There is no inheritance — order in the array determines precedence, and ignores live inside the array as their own object.
Here is the eslintrc above rewritten as flat config:
// eslint.config.js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginReact from 'eslint-plugin-react'
import pluginReactHooks from 'eslint-plugin-react-hooks'
import globals from 'globals'
export default [
{ ignores: ['dist/', 'build/', 'coverage/', '*.config.js'] },
js.configs.recommended,
...tseslint.configs.recommended,
pluginReact.configs.flat.recommended,
{
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
globals: { ...globals.browser, ...globals.node },
parserOptions: { ecmaFeatures: { jsx: true } },
},
plugins: { 'react-hooks': pluginReactHooks },
settings: { react: { version: 'detect' } },
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
...pluginReactHooks.configs.recommended.rules,
},
},
{
files: ['**/*.test.ts', '**/*.test.tsx'],
languageOptions: { globals: globals.jest },
rules: { '@typescript-eslint/no-explicit-any': 'off' },
},
]The fastest path is the official codemod: npx @eslint/migrate-config .eslintrc.json. It reads the eslintrc, emits an equivalent eslint.config.js, and flags items that need manual review. The generated file often uses FlatCompat from @eslint/eslintrc as a translation layer — that ships fine, but native plugin imports (as above) are smaller and faster. After verifying that npx eslint . produces matching output, delete .eslintrc.json, the .eslintignore file, and any eslintConfig field in package.json.
Coexistence period: ESLINT_USE_FLAT_CONFIG and the --eslint-config flag
ESLint 9 ships both code paths but defaults to flat config. To keep using .eslintrc.json on ESLint 9 without migrating, set the environment variable ESLINT_USE_FLAT_CONFIG=false before running ESLint. CI configurations often wire this into the npm script:
{
"scripts": {
"lint": "ESLINT_USE_FLAT_CONFIG=false eslint . --ext .js,.ts,.tsx"
}
}On Windows the syntax differs: cross-env ESLINT_USE_FLAT_CONFIG=false eslint . via the cross-env package is the portable form. The flag is a stopgap, not a long-term answer. ESLint 10 removed the legacy code path entirely, so the environment variable does nothing on 10+. Projects that genuinely cannot migrate yet should install @eslint/eslintrc and add a thin eslint.config.js that uses FlatCompat to translate the existing .eslintrc.json:
// eslint.config.js — minimal compat shim
import { FlatCompat } from '@eslint/eslintrc'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const compat = new FlatCompat({ baseDirectory: __dirname })
export default [
...compat.extends('./.eslintrc.json'),
]This keeps the team unblocked while the real migration happens out-of-band. Treat it as a temporary bridge — every release of ESLint and the major plugins moves further from the eslintrc model.
package.json eslintConfig and ignorePatterns vs .eslintignore vs flat ignores
There are three places ESLint accepts ignores and two places to put the config itself. The choices interact subtly — here is the map.
// package.json — inline eslintConfig field (legacy)
{
"name": "my-app",
"scripts": { "lint": "eslint ." },
"eslintConfig": {
"root": true,
"extends": ["eslint:recommended"],
"rules": { "no-console": "warn" }
}
}The eslintConfig field in package.json is equivalent to an .eslintrc.json at the same level — same shape, same merge rules. It is convenient for small projects but loses syntax highlighting in editors that do not know to treat the field as an ESLint config. Flat config has no equivalent inline form — eslint.config.js is always a separate file.
| Mechanism | Format | Works with | Notes |
|---|---|---|---|
.eslintignore | gitignore syntax, one pattern per line | eslintrc only | Removed from flat config; do not create on new projects |
ignorePatterns in .eslintrc.json | JSON array of patterns | eslintrc only | Same syntax as .eslintignore; inline alternative |
{ ignores: [...] } config object | Array entry in flat config | flat config only | The flat-config replacement for both above |
--ignore-pattern CLI flag | Per-invocation | Both formats | Ad-hoc ignores for one-off lint runs |
.eslintrc.json vs eslint.config.js: field-by-field comparison
| Concept | .eslintrc.json (legacy) | eslint.config.js (flat) |
|---|---|---|
| File type | Strict JSON (also JS/YAML variants) | JavaScript module (ESM or CJS) |
| Comments | No (strict JSON) | Yes (it's JavaScript) |
| Plugins | plugins: ["react"] (string name) | plugins: { react: pluginReact } (imported) |
| Shareable configs | extends: ["airbnb"] | Spread imported config into the array |
| Plugin configs | extends: ["plugin:react/recommended"] | pluginReact.configs.flat.recommended |
| Parser | parser: "@typescript-eslint/parser" | languageOptions.parser: tseslintParser |
| Env globals | env: { browser: true } | languageOptions.globals: globals.browser (via globals pkg) |
| Per-file overrides | overrides: [{ files, rules }] | Additional config objects in the array with files |
| Ignores | .eslintignore or ignorePatterns | { ignores: [...] } object in the array |
| Inheritance | Walks up directories, merges parent configs | None — single config file at the project root |
| Inline in package.json | Yes (eslintConfig field) | No |
| Default in ESLint 9+ | Opt-in via env var | Yes |
The pattern when reading both sides: legacy uses strings as identifiers and filesystem walking for inheritance; flat uses real ES imports and explicit array order. Once you internalize that shift, the migration becomes mechanical.
Key terms
- .eslintrc.json
- The legacy ESLint config file — a strict-JSON document at the project root (or anywhere in the tree) that lists rules, plugins, parsers, env globals, and overrides. Deprecated in favor of
eslint.config.jsas of ESLint 9. - flat config (eslint.config.js)
- The modern ESLint config format introduced in ESLint 8.21 (experimental) and made default in ESLint 9. A JavaScript module exporting an array of config objects. No inheritance, no string magic for plugins — everything is imported.
- shareable config
- An npm package conventionally named
eslint-config-<name>that exports a reusable ESLint config. Consumers reference it viaextends: ["<name>"]in eslintrc, or by importing and spreading it in flat config. - plugin
- An npm package conventionally named
eslint-plugin-<name>that ships custom rules, processors, and shareable configs. Plugins must be listed inpluginsin eslintrc; in flat config they are imported and added to thepluginsfield of a config object. - severity
- How loud ESLint is about a rule violation.
off(or0) suppresses;warn(1) reports without failing the run;error(2) fails the run with exit code 1. - FlatCompat
- A helper exported by the
@eslint/eslintrcpackage that translates legacy eslintrc-style configs into flat config objects at runtime. Used as a bridge during migration; not intended as a permanent dependency.
Frequently asked questions
Is .eslintrc.json deprecated?
Yes, the eslintrc family of config files (.eslintrc.json, .eslintrc.js, .eslintrc.yml, and the eslintConfig field in package.json) is deprecated in favor of the flat config format (eslint.config.js). ESLint 9, released April 2024, made flat config the default but kept legacy support behind an opt-in flag. ESLint 10, released in late 2025, removed legacy eslintrc support from the core CLI — projects still on .eslintrc.json must either install the @eslint/eslintrc backward-compat package or migrate. New projects should always start with eslint.config.js. Existing projects on .eslintrc.json still work today through compatibility shims, but you should plan the migration before the next major framework upgrade, because plugin authors are dropping eslintrc support in their newer releases. The flat config format is simpler to reason about (it is a regular JavaScript array of config objects) and faster to load.
What's the difference between .eslintrc.json and eslint.config.js?
.eslintrc.json is the legacy configuration format — a static JSON object that ESLint loads from the nearest .eslintrc.json walking up from the file being linted, then merges with parent configs by inheritance. It uses string identifiers for plugins and shareable configs (extends: "plugin:react/recommended") and a separate .eslintignore file for ignore patterns. eslint.config.js is the modern flat config — a JavaScript module that default-exports an array of config objects. Plugins and parsers are imported as real ES modules (no string magic), ignores live inside the config array as objects with an ignores field, and there is no inheritance — order of objects in the array determines precedence. Flat config is faster, more predictable, and works with modern bundlers. The trade-off: it cannot be pure JSON because it imports real modules.
Can I still use .eslintrc.json in ESLint 9?
Yes, but you must opt in. ESLint 9 made flat config the default, so a fresh install will look for eslint.config.js first and ignore .eslintrc.json. To keep using the legacy format on ESLint 9, set the environment variable ESLINT_USE_FLAT_CONFIG=false before running ESLint, or use the older --eslint-config flag in CI. This is a stopgap — ESLint 10 (released late 2025) removed the legacy code path entirely from the core CLI. To keep .eslintrc.json working on ESLint 10+, install the @eslint/eslintrc compatibility package, which provides a FlatCompat helper that translates eslintrc-style configs into flat config objects you can drop into eslint.config.js. The cleanest path forward is to migrate the config and remove the shim entirely.
Why does my .eslintrc.json work but extends fails to resolve?
Two common causes. First, the package implementing the shareable config is not installed in node_modules — extends only resolves packages, not local files unless you use a relative path. Run npm install eslint-config-airbnb (or whichever package name extends references) and confirm it lands in package.json. Second, the extends string format is wrong. For shareable configs the prefix eslint-config- is implicit: extends: "airbnb" resolves to the package eslint-config-airbnb. For plugin configs you need the plugin: prefix and the plugin short name: extends: "plugin:react/recommended" loads the recommended config exported by eslint-plugin-react. Mixing these up — extends: "eslint-plugin-react" instead of "plugin:react/recommended" — silently fails to resolve. Run npx eslint --print-config <file> to see the merged config and pinpoint which extends entry is missing.
How do I migrate .eslintrc.json to flat config quickly?
Use the official codemod. Run npx @eslint/migrate-config .eslintrc.json from the project root. The tool reads your existing config, generates an equivalent eslint.config.js (or .mjs), and prints a list of items that need manual review — usually plugin import names and any custom parsers. Review the generated file, install any plugins it imports (the migration tool flags missing ones), run npx eslint . to confirm the lint output matches what you had before, then delete .eslintrc.json and .eslintignore. For complex configs the codemod produces a config that imports @eslint/eslintrc and uses FlatCompat to translate the extends array — that is correct and ships fine, but cleaning it up to use direct plugin imports gives you a smaller, faster config. Budget an hour for a medium project.
How do I share an .eslintrc.json across multiple repos?
Publish a shareable config package. The convention is to name the package eslint-config-yourname so consumers can write extends: "yourname" instead of extends: "eslint-config-yourname" — ESLint strips the prefix automatically. The package exports a JavaScript object with the same shape as .eslintrc.json: rules, env, parserOptions, plugins (as string names), and optionally an extends field pointing at other shareable configs you want to compose. Publish to npm or a private registry. Consumers install the package and add extends: "yourname" to their .eslintrc.json. For the flat config era, the convention is the same but consumers import your config directly: import myConfig from "eslint-config-yourname" and spread it into their array. The migration path is to ship both an eslintrc and a flat export from the same package during the transition.
What is the difference between extends and overrides[].extends?
Top-level extends applies the listed shareable or plugin configs to every file ESLint lints in that directory tree. It defines the baseline. overrides[].extends applies additional configs only to files matching the overrides entry files glob — useful when one shareable config is right for source files but a different one fits tests, JSX, or TypeScript files. For example, the top-level extends might be ["eslint:recommended"] for plain JavaScript, with an overrides entry that adds extends: ["plugin:@typescript-eslint/recommended"] for files: ["**/*.ts", "**/*.tsx"]. The overrides extends is merged on top of the top-level extends for matching files only — you do not need to re-list the base configs. Merge order is top-level extends, then top-level rules, then each matching overrides entry in array order.
Why are my plugin rules not recognized in .eslintrc.json?
Three things to check. First, the plugin is listed in the plugins array — plugins: ["react"] tells ESLint to load eslint-plugin-react. Without that, the rule name react/no-unescaped-entities is unknown. Second, the package is actually installed: npm ls eslint-plugin-react should resolve. Third, the rule name uses the correct plugin short name. The mapping is: package eslint-plugin-react exposes the short name react, so rules are react/no-unescaped-entities. Package @typescript-eslint/eslint-plugin uses the short name @typescript-eslint, so rules are @typescript-eslint/no-unused-vars. Scoped plugin packages keep the scope in the short name. If the plugin is listed and installed but rules still fail, run npx eslint --print-config <file> and grep for the plugin name in the output to see whether it loaded.
Further reading and primary sources
- ESLint Docs — Configuration Files (legacy) — Authoritative reference for the legacy .eslintrc.json field set
- ESLint Docs — Configuration Files (flat) — Current reference for eslint.config.js and the flat config format
- ESLint Migration Guide to v9.0.0 — Official upgrade and migration notes for ESLint 9, including the flat config switch
- @eslint/migrate-config — Codemod that converts .eslintrc.json to eslint.config.js automatically
- @eslint/eslintrc backward-compat package — FlatCompat helper for running legacy eslintrc configs under ESLint 10+