ESLint 10 just dropped, and it's not a minor update. The .eslintrc configuration system you've been clinging to? Completely removed—not deprecated, removed. The --env flag? Gone. Node.js 18? Not supported anymore.
This is the ESLint team's statement: flat config is the only way forward.
If you've been putting off the migration, January 2026 is your deadline. ESLint 9 is now in maintenance mode. ESLint 10 is the present. And this guide will get you there without breaking your CI pipeline.
What's New in ESLint 10
Let's start with what changed before we dive into migration.
Node.js 20.19.0+ Required
ESLint 10 drops support for Node.js 18, 19, 21, and 23. You need:
- Node.js ^20.19.0 (20.19.0 or higher in 20.x)
- Node.js ^22.13.0 (22.13.0 or higher in 22.x LTS)
- Node.js >=24.0.0 (24.x Current)
Check your CI/CD pipelines. This will break builds on older Node versions immediately.
node --version # Must be v20.19.0+
Complete Removal of .eslintrc
This is the big one. ESLint 10 removes everything related to the legacy configuration:
| Removed | Replacement |
|---|---|
.eslintrc.* files |
eslint.config.js |
.eslintignore |
ignores array in config |
--env flag |
globals in languageOptions
|
--rulesdir flag |
Direct plugin imports |
--resolve-plugins-relative-to |
ES module imports |
/* eslint-env */ comments |
Now reported as errors |
ESLINT_USE_FLAT_CONFIG env var |
Always flat config |
LegacyESLint API |
Removed entirely |
If your project still uses .eslintrc.json, ESLint 10 will simply ignore it.
New Configuration File Lookup
ESLint 10 changes how it finds config files:
ESLint 9 (old behavior):
Search from CWD upward for eslint.config.js
ESLint 10 (new behavior):
Search from each linted file's directory upward for eslint.config.js
This is huge for monorepos. Each package can now have its own eslint.config.js, and ESLint will find it automatically.
my-monorepo/
├── eslint.config.js # Root config (fallback)
├── packages/
│ ├── web/
│ │ ├── eslint.config.js # Web-specific config
│ │ └── src/
│ └── api/
│ ├── eslint.config.js # API-specific config
│ └── src/
JSX Reference Tracking
ESLint 10 now tracks JSX element references for accurate scope analysis. This means:
- Custom components are properly tracked as references
-
no-unused-varsworks correctly with JSX - Some existing code might trigger new warnings (false positives in v9)
// ESLint 10 now correctly sees Button as a reference
import { Button } from './components';
function App() {
return <Button>Click me</Button>; // Button is tracked
}
Updated eslint:recommended
The recommended ruleset has been updated. Run ESLint after migration to catch any new violations.
Step-by-Step Migration from ESLint 9
Step 1: Check Node.js Version
node --version
# If below v20.19.0, upgrade first
Update your CI/CD to use Node.js 20.x LTS or 22.x.
Step 2: Update ESLint and Dependencies
npm install eslint@10 --save-dev
# Update TypeScript ESLint if using TypeScript
npm install @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest --save-dev
# Update other plugins
npm install eslint-plugin-react@latest eslint-plugin-react-hooks@latest --save-dev
Step 3: Migrate Config (If You Haven't Already)
If you're still on .eslintrc, use the migration tool:
npx @eslint/migrate-config .eslintrc.json
This generates an eslint.config.mjs file. Review and adjust it.
Step 4: Update Your Flat Config for v10 Compatibility
If you already have eslint.config.js, check for these v10-specific updates:
Remove eslint-env Comments
// ❌ ESLint 10 reports this as an error
/* eslint-env browser, node */
// ✅ Use globals in config instead
import globals from "globals";
export default [
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
}
}
}
];
Update SourceCode Method Usage (For Plugin Authors)
// ❌ Removed in ESLint 10
sourceCode.getTokenOrCommentBefore(node);
// ✅ Use replacement
sourceCode.getTokenBefore(node, { includeComments: true });
Update Context Methods (For Rule Authors)
// ❌ Removed in ESLint 10
context.getFilename();
context.getCwd();
context.getSourceCode();
// ✅ Use replacements
context.filename;
context.cwd;
context.sourceCode;
Step 5: Delete Legacy Files
rm .eslintrc.* .eslintignore 2>/dev/null || true
Step 6: Update VS Code Settings
{
"eslint.useFlatConfig": true
}
Note: The eslint.useFlatConfig setting is now the default. You can remove it.
Step 7: Test
npx eslint .
Fix any new violations from the updated eslint:recommended or JSX tracking changes.
Complete ESLint 10 Config Examples
TypeScript Project
// eslint.config.js
import js from "@eslint/js";
import typescript from "typescript-eslint";
import globals from "globals";
export default typescript.config(
js.configs.recommended,
...typescript.configs.recommendedTypeChecked,
{
languageOptions: {
globals: globals.node,
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
files: ["**/*.js", "**/*.mjs"],
...typescript.configs.disableTypeChecked,
},
{
ignores: ["dist/**", "node_modules/**"],
}
);
React + TypeScript (Next.js)
// eslint.config.mjs
import js from "@eslint/js";
import typescript from "typescript-eslint";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import jsxA11y from "eslint-plugin-jsx-a11y";
import next from "@next/eslint-plugin-next";
import globals from "globals";
export default typescript.config(
js.configs.recommended,
...typescript.configs.recommended,
// React + JSX
{
files: ["**/*.{jsx,tsx}"],
plugins: {
react,
"react-hooks": reactHooks,
"jsx-a11y": jsxA11y,
},
languageOptions: {
globals: globals.browser,
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
settings: {
react: { version: "detect" },
},
rules: {
...react.configs.recommended.rules,
...react.configs["jsx-runtime"].rules,
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
},
},
// Next.js
{
plugins: {
"@next/next": next,
},
rules: {
...next.configs.recommended.rules,
...next.configs["core-web-vitals"].rules,
},
},
{
ignores: [".next/**", "node_modules/**", "out/**"],
}
);
Monorepo with Package-Specific Configs
// Root: eslint.config.js
import js from "@eslint/js";
import typescript from "typescript-eslint";
// Base config that all packages inherit
export default typescript.config(
js.configs.recommended,
...typescript.configs.recommended,
{
ignores: ["**/dist/**", "**/node_modules/**"],
}
);
// packages/web/eslint.config.js
import baseConfig from "../../eslint.config.js";
import react from "eslint-plugin-react";
import globals from "globals";
export default [
...baseConfig,
{
files: ["**/*.{jsx,tsx}"],
plugins: { react },
languageOptions: {
globals: globals.browser,
},
rules: {
...react.configs.recommended.rules,
},
},
];
ESLint 10's new lookup algorithm will automatically use the closest eslint.config.js to each file.
Common Migration Errors and Fixes
Error: "ESLint requires Node.js version ^20.19.0 || ^22.0.0 || >=24.0.0"
Fix: Upgrade Node.js to 20.19.0 or later.
# Using nvm
nvm install 22
nvm use 22
Error: "eslint-env is no longer supported"
Fix: Remove /* eslint-env */ comments and use globals:
import globals from "globals";
{
languageOptions: {
globals: globals.browser
}
}
Error: "ConfigArray.normalize is not a function"
Fix: Update @eslint/eslintrc to the latest version:
npm install @eslint/eslintrc@latest
Error: "Cannot find config" in monorepo
Fix: ESLint 10 looks from the file's directory, not CWD. Either:
- Add
eslint.config.jsto each package, or - Run ESLint from the root with explicit file patterns
Warning: New violations from JSX tracking
Fix: These are likely real issues that v9 missed. Review each one. If it's a false positive, disable the rule for that line.
The Migration Checklist
Use this checklist for your migration:
- [ ] Node.js is v20.19.0+ or v22.x
- [ ] ESLint updated to v10
- [ ] All plugins updated to v10-compatible versions
- [ ] Config migrated to
eslint.config.js - [ ] All
/* eslint-env */comments removed - [ ]
.eslintrc.*files deleted - [ ]
.eslintignoredeleted (useignoresin config) - [ ] CI/CD updated to use Node.js 20+
- [ ] VS Code settings updated
- [ ] Full lint run passes
Should You Migrate Now?
Yes. Here's why:
- ESLint 9 is in maintenance mode—no new features, only critical fixes
- Plugin ecosystem is moving—popular plugins are dropping v9 support
- The new lookup algorithm is powerful—especially for monorepos
- JSX tracking fixes real bugs—your existing code might have issues you didn't know about
The migration is mostly mechanical. If you already moved to flat config in v9, you're 90% done. If you're still on .eslintrc, now is the time—there's no more runway.
Conclusion
ESLint 10 is a clean break from the past. The legacy configuration system is gone for good. But what you get in return is:
- Simpler mental model: One file, one format, no magic
- Better monorepo support: Per-package configs that just work
- More accurate linting: JSX tracking catches real issues
- Future-proof: You're on the path the ecosystem is taking
The migration might take an hour or two, but you'll be set for the next several years of ESLint releases.
One less piece of tech debt. Now go update those configs! 🚀
🛠️ Developer Toolkit: This post first appeared on the Pockit Blog.
Need a Regex Tester, JWT Decoder, or Image Converter? Use them on Pockit.tools or install the Extension to avoid switching tabs. No signup required.
Top comments (0)