| name | eslint |
| description | This skill should be used when the user asks to "set up ESLint", "configure ESLint rules", "fix ESLint errors", "migrate to flat config", or needs guidance on JavaScript/TypeScript linting best practices. |
ESLint Best Practices
Provides guidance for configuring ESLint using the modern flat config format (eslint.config.js), selecting and tuning rules, managing plugins, and integrating linting into CI/CD workflows.
Core Concepts
- Flat config (
eslint.config.js) is the current standard. The legacy .eslintrc.* format is deprecated as of ESLint v9.
- Configuration objects are merged in order — later objects override earlier ones for the same rule.
- Rule severities:
"off" (0) | "warn" (1) | "error" (2).
- Plugins extend ESLint with additional rules, processors, and languages. Reference as
pluginName/ruleName.
Setup
Quick initialization (recommended)
npm init @eslint/config@latest
This generates eslint.config.js interactively. Requires Node.js ^20.19.0, ^22.13.0, or >=24.
Manual setup
npm install --save-dev eslint@latest @eslint/js@latest
Create eslint.config.js:
import { defineConfig } from "eslint/config";
import js from "@eslint/js";
export default defineConfig([
{
files: ["**/*.js"],
plugins: { js },
extends: ["js/recommended"],
rules: {
"no-unused-vars": "warn",
"no-undef": "error",
},
},
]);
Run linting:
npx eslint src/
Flat Config Structure
Every configuration object accepts these keys:
| Key | Purpose |
|---|
name | Label for debugging (use plugin/scope convention) |
files | Glob patterns this object applies to |
ignores | Glob patterns to exclude (global if no other keys present) |
extends | Inherit from plugin configs, shareable configs, or objects |
plugins | Register plugins by namespace |
rules | Rule names mapped to severity or [severity, ...options] |
languageOptions | ecmaVersion, sourceType, globals, parser, parserOptions |
linterOptions | noInlineConfig, reportUnusedDisableDirectives, reportUnusedInlineConfigs |
settings | Shared data available to all rules |
processor | Extracts JS from non-JS files (e.g., Markdown) |
File targeting
{ files: ["src/**/*.{js,ts}"], rules: { ... } }
{ files: ["**/*.js"], ignores: ["**/*.test.js"], rules: { ... } }
Global ignores (use globalIgnores)
import { defineConfig, globalIgnores } from "eslint/config";
export default defineConfig([
globalIgnores(["dist/", "coverage/", "*.min.js"]),
]);
Use globalIgnores() — not bare ignores keys — for project-wide exclusions. This is the clearest and least error-prone pattern.
Import .gitignore patterns
import { includeIgnoreFile } from "@eslint/compat";
import { fileURLToPath } from "node:url";
const gitignorePath = fileURLToPath(new URL(".gitignore", import.meta.url));
export default defineConfig([
includeIgnoreFile(gitignorePath),
]);
Rule Configuration Best Practices
Prefer "error" for enforced standards
Use "error" for rules that block bad code from merging. Reserve "warn" for violations that require human judgment or incremental adoption.
rules: {
"no-var": "error",
"prefer-const": "error",
"eqeqeq": ["error", "always"],
"no-console": "warn",
}
Disable inline comments sparingly
Use // eslint-disable-next-line rule-name -- reason with a mandatory reason. Never disable wholesale with /* eslint-disable */ without scoping and justification.
console.log("Starting server...");
Enable detection of stale disable comments:
linterOptions: {
reportUnusedDisableDirectives: "error",
}
File-pattern overrides instead of inline disables
Disable rules for test files via configuration, not inline comments:
{
files: ["**/*.test.{js,ts}", "**/*.spec.{js,ts}"],
rules: {
"no-unused-expressions": "off",
},
}
Plugin Usage
Register and use a plugin
import jsdoc from "eslint-plugin-jsdoc";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
plugins: { jsdoc },
rules: {
"jsdoc/require-description": "error",
"jsdoc/check-values": "error",
},
},
]);
The plugin namespace is the property name in plugins. Convention: strip the eslint-plugin- prefix.
Extend a plugin's recommended config
import react from "eslint-plugin-react";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.{jsx,tsx}"],
plugins: { react },
extends: ["react/recommended"],
languageOptions: {
parserOptions: { ecmaFeatures: { jsx: true } },
},
},
]);
TypeScript Support
Install the TypeScript parser and plugin:
npm install --save-dev typescript-eslint
import tseslint from "typescript-eslint";
import { defineConfig } from "eslint/config";
export default defineConfig([
...tseslint.configs.recommended,
{
files: ["**/*.ts", "**/*.tsx"],
rules: {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/explicit-function-return-type": "off",
},
},
]);
Linter Options
{
linterOptions: {
noInlineConfig: false,
reportUnusedDisableDirectives: "error",
reportUnusedInlineConfigs: "warn",
},
}
Debugging Configuration
Inspect which config objects apply to a file:
npx eslint --inspect-config
npx eslint --print-config file.js
CI/CD Integration
Add to package.json:
{
"scripts": {
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix"
}
}
Run npm run lint in CI. ESLint exits with code 1 when any "error"-severity rule triggers, blocking merges.
Quick Reference: Rule Severity
| Value | Meaning |
|---|
"off" / 0 | Disabled |
"warn" / 1 | Warning (exit code 0) |
"error" / 2 | Error (exit code 1) |
Additional Resources
Reference Files
references/configuration-guide.md — Detailed flat config patterns, cascading, TypeScript config files, monorepo setup
references/rules-and-plugins.md — Commonly used rules reference and popular plugin ecosystem
Example Files
examples/eslint.config.js — Production-ready config for a TypeScript project with React