eslint-9
ESLint 9 Flat Config Patterns
Overview
ESLint 9 uses flat config (eslint.config.js) as the default. The legacy .eslintrc.* format is deprecated.
Key differences from .eslintrc:
- Single config file, explicit imports — no magic
extendsstrings. - Config is an exported array of config objects, applied in order.
- Plugins are imported directly, not referenced by string name.
- Glob patterns in
filesreplace the oldoverridessystem.
Basic Setup
JavaScript
// eslint.config.js
import js from "@eslint/js";
export default [
js.configs.recommended,
{
rules: {
"no-unused-vars": "warn",
"no-console": "warn",
},
},
];
TypeScript
npm install -D eslint @eslint/js typescript-eslint
// eslint.config.ts
import js from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(js.configs.recommended, tseslint.configs.recommended, {
rules: {
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
},
});
typescript-eslint exports a config() helper that provides type safety and handles array flattening.
TypeScript Config Files
ESLint 9 supports eslint.config.ts natively — no extra packages needed. Use it for full type checking of your config:
import type { Linter } from "eslint";
defineConfig
ESLint provides defineConfig for type-safe configs without typescript-eslint:
import { defineConfig } from "eslint/config";
import js from "@eslint/js";
export default defineConfig([
js.configs.recommended,
{
files: ["**/*.js"],
rules: {
"no-console": "warn",
},
},
]);
defineConfig auto-flattens nested arrays and provides IntelliSense for rule names.
Adding Plugins
Plugins are imported and assigned to a namespace:
import reactPlugin from "eslint-plugin-react";
import reactHooksPlugin from "eslint-plugin-react-hooks";
export default tseslint.config(js.configs.recommended, tseslint.configs.recommended, {
files: ["**/*.{tsx,jsx}"],
plugins: {
react: reactPlugin,
"react-hooks": reactHooksPlugin,
},
rules: {
"react/jsx-no-target-blank": "error",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
},
settings: {
react: { version: "detect" },
},
});
Many plugins now ship flat config presets. Check for plugin.configs["flat/recommended"] or similar exports.
Global Ignores
Use a config object with only ignores (no files) for global ignore patterns:
export default tseslint.config(
{ ignores: ["dist/", "node_modules/", "*.config.*", ".next/"] },
js.configs.recommended,
tseslint.configs.recommended,
);
Global ignores must be a standalone object — not combined with rules or files.
File-Specific Overrides
Use files globs to scope rules:
export default tseslint.config(
js.configs.recommended,
tseslint.configs.recommended,
// Stricter rules for source code
{
files: ["src/**/*.{ts,tsx}"],
rules: {
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/no-explicit-any": "error",
},
},
// Relaxed rules for tests
{
files: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
},
},
// Config files (CommonJS)
{
files: ["*.config.{js,cjs,mjs}"],
languageOptions: {
sourceType: "commonjs",
},
},
);
Prettier Integration
Use eslint-config-prettier to disable formatting rules that conflict with Prettier:
import prettierConfig from "eslint-config-prettier";
export default tseslint.config(
js.configs.recommended,
tseslint.configs.recommended,
prettierConfig, // must be last to override formatting rules
);
Don't use eslint-plugin-prettier — let Prettier handle formatting separately.
Common React + TypeScript Config
A complete config for a React + TypeScript project:
// eslint.config.ts
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import reactPlugin from "eslint-plugin-react";
import reactHooksPlugin from "eslint-plugin-react-hooks";
import prettierConfig from "eslint-config-prettier";
export default tseslint.config(
{ ignores: ["dist/", "node_modules/", ".next/"] },
js.configs.recommended,
tseslint.configs.recommended,
{
files: ["**/*.{tsx,jsx}"],
plugins: {
react: reactPlugin,
"react-hooks": reactHooksPlugin,
},
rules: {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
},
settings: {
react: { version: "detect" },
},
},
{
rules: {
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
"@typescript-eslint/consistent-type-imports": ["warn", { prefer: "type-imports" }],
},
},
prettierConfig,
);
Migration from .eslintrc
- Run the migration tool:
npx @eslint/migrate-config .eslintrc.json - Review the generated
eslint.config.mjs— the tool handles most conversions. - Replace string-based plugin references with direct imports.
- Replace
extendsstrings with imported config arrays. - Convert
overridesto separate config objects withfilesglobs. - Delete
.eslintrc.*and.eslintignorefiles.
Debugging
Inspect the resolved config for any file:
npx eslint --inspect-config
This launches an interactive viewer showing which rules apply to each file and where they come from.