cheat sheet
eslint
Package-level reference for eslint on npm — install variants, flat config (v9) vs legacy .eslintrc, typescript-eslint, plugin ecosystem, and alternatives.
eslint
What it is
eslint is the long-standing pluggable static-analysis tool for JavaScript and TypeScript, created by Nicholas C. Zakas in 2013. It parses code into an AST (default espree; TypeScript uses @typescript-eslint/parser) and runs configured rules across the tree, emitting warnings and errors. Rules ship in core, in framework plugins (eslint-plugin-react, eslint-plugin-vue), and in language plugins (typescript-eslint).
The configuration model split in v9: flat config (eslint.config.js — single file, explicit array of config objects) is now mandatory, replacing the legacy hierarchical .eslintrc.* files that walked the directory tree merging configs.
Install
# Always a devDep
npm install -D eslint
pnpm add -D eslint
yarn add -D eslint
bun add -d eslint
Output: eslint binary on PATH; expects an eslint.config.js at repo root.
# Scaffold a flat config interactively (v9+)
npm init @eslint/config@latest
Output: writes eslint.config.js (or .mjs / .cjs) plus any deps it selects (e.g. typescript-eslint, eslint-plugin-react).
# Day-to-day
npx eslint . # lint everything
npx eslint . --fix # apply auto-fixes
npx eslint src/foo.ts --max-warnings 0 # strict mode
Output: per-file diagnostics with <line>:<col> <severity> <message> <rule-id>; exits non-zero on any error.
Versioning & Node support
- Current major line is
9.x(released April 2024).8.xis now in extended LTS only (security fixes);7.xis EOL. v9requires Node 18.18+, 20.9+, or 21.1+ and dropped support for Node 16.- Flat config (
eslint.config.js) is mandatory inv9— legacy.eslintrc.*files are ignored. Migration shim@eslint/eslintrclets youcompatover old configs during transition. eslintships as CJS but understands ESM configs. Always a dev dependency.- Loose semver — major bumps typically remove rules and config formats.
Package metadata
- Maintainer: OpenJS Foundation / ESLint TSC (Nicholas C. Zakas + core team)
- Project home: github.com/eslint/eslint
- Docs: eslint.org/docs
- npm: npmjs.com/package/eslint
- License: MIT
- First released: 2013
- Downloads: tens of millions per week — top-5 dev tool on npm.
Peer dependencies & extras
ESLint core has minimal peers; all the surface comes from plugins:
| Package | Purpose |
|---|---|
typescript-eslint (single package, v8+) | TypeScript parser + recommended rule sets. Replaced the older @typescript-eslint/parser + @typescript-eslint/eslint-plugin pair for flat config; both still exist for legacy configs. |
@eslint/js | Official core JS rule presets exposed as flat-config objects (js.configs.recommended). |
eslint-plugin-react / eslint-plugin-react-hooks | React + hooks rules. Hooks plugin is essentially mandatory for React projects. |
eslint-plugin-vue | Vue 2/3 rules. |
eslint-plugin-svelte | Svelte rules. |
eslint-plugin-astro | .astro support via astro-eslint-parser. |
eslint-plugin-import / eslint-plugin-import-x | Import-graph validation. import-x is the maintained fork most flat-config projects use. |
eslint-plugin-unicorn | Opinionated extras (regex safety, modern API preference, etc.). |
eslint-plugin-jsx-a11y | Accessibility rules for JSX. |
eslint-plugin-n | Node-specific rules (successor to eslint-plugin-node). |
eslint-config-prettier | Disables stylistic rules that conflict with Prettier. Almost always used alongside Prettier. |
eslint-plugin-prettier | Runs Prettier as an ESLint rule (slower; prefer eslint-config-prettier). |
Alternatives
| Tool | Trade-off |
|---|---|
| @biomejs/biome | Rust, single binary, linter + formatter, 25–35× faster. Coverage ~95% of common ESLint rules; missing some plugins. Strong pick for new projects. |
| oxlint (part of oxc) | Rust, ESLint-compatible, very fast, intended to run alongside ESLint for the rules it supports. Younger, narrower rule coverage. |
| typescript-eslint | A layer on top of ESLint, not an alternative — gives TS parsing + type-aware rules. Always paired with ESLint, never replacing it. |
| deno lint | Bundled with Deno. Tied to the Deno ecosystem. |
| standard / standardx / xo | Wrapped, opinionated ESLint configs. Replace your config, not ESLint. |
Common gotchas
v9flat config is mandatory. Many plugins lagged the migration — projects that bumped tov9hit "Cannot use this plugin with flat config" errors for months. Use@eslint/compat'sfixupPluginRules()to wrap legacy plugins until they ship native flat-config exports.--fixis destructive for some rules. Rules likeno-unused-vars(withargsIgnorePattern) orprefer-constare safe; rules with auto-fixers that rewrite logic (e.g.unicorn/prefer-spread) can change semantics on edge cases. Reviewgit diffafter every--fixrun on production code.- Type-aware rules are slow.
typescript-eslint'srecommended-type-checkedpreset parses the TS program — adds seconds-to-minutes to lint runs on large codebases. ConfigurelanguageOptions.parserOptions.projectand consider scoping type-aware rules to a separateeslint.config.jsentry. - Plugin resolution paths differ across managers. pnpm's strict layout sometimes hides plugins. Flat config resolves plugins from the config file directly (via
import), which sidesteps this — another reason to migrate off.eslintrc. - Disabling a rule inline is a 3-form trap.
// eslint-disable-next-line,/* eslint-disable */, and/* eslint-disable-next-line */(block-comment, next-line variant) behave differently. The first two are the safe choices; mix them up and you'll silently disable far more than you intended. ignoresin flat config replaces.eslintignore. v9 deprecated.eslintignore. Add{ ignores: ["dist/**", ".astro/**"] }as the first entry of the flat config array — a standalone object with onlyignoresbecomes a "global ignore".- Performance: rule cost varies wildly. Run
eslint --debugorTIMING=1 eslint .to surface which rules are slow.import/no-cycleand any type-aware rule routinely dominate runtime; consider running them in CI only.
Real-world recipes
The patterns that come up when you're actually wiring ESLint into a fresh repo.
Flat config from scratch (TS + React + Prettier)
// eslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import prettier from "eslint-config-prettier";
export default [
{ ignores: ["dist/**", "build/**", ".next/**", "coverage/**"] },
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
parserOptions: {
project: ["./tsconfig.json"],
tsconfigRootDir: import.meta.dirname,
},
},
plugins: { react, "react-hooks": reactHooks },
rules: {
...react.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
"react/react-in-jsx-scope": "off",
},
settings: { react: { version: "detect" } },
},
prettier,
];
prettier must be last so it overrides any stylistic rules from earlier configs.
Monorepo flat config
A single root config that varies per package:
// eslint.config.js
import baseConfig from "./eslint.config.base.js";
export default [
...baseConfig,
{
files: ["packages/api/**/*.ts"],
rules: { "no-console": ["error", { allow: ["error"] }] },
},
{
files: ["packages/web/**/*.{ts,tsx}"],
languageOptions: { globals: { window: "readonly", document: "readonly" } },
},
];
Each files block scopes by path; configs are merged in order (last wins).
Custom rule — ban an internal import
// eslint-custom-rules/no-internal-imports.js
export default {
meta: { type: "problem", schema: [], messages: { internalImport: "Do not import from internal paths." } },
create(context) {
return {
ImportDeclaration(node) {
if (node.source.value.includes("/internal/")) {
context.report({ node, messageId: "internalImport" });
}
},
};
},
};
// eslint.config.js
import customRules from "./eslint-custom-rules/index.js";
export default [
{
plugins: { custom: { rules: customRules } },
rules: { "custom/no-internal-imports": "error" },
},
];
Per-rule severity per file
{
files: ["scripts/**/*.ts", "tools/**/*.ts"],
rules: {
"no-console": "off", // scripts can console.log
"@typescript-eslint/no-explicit-any": "off",
},
}
Disable a rule for one line
// eslint-disable-next-line no-console
console.log("intentional");
const x = 1; // eslint-disable-line @typescript-eslint/no-unused-vars
Block-disable for a region:
/* eslint-disable no-console */
console.log("a");
console.log("b");
/* eslint-enable no-console */
Production deployment
ESLint is dev-time only; "deployment" is CI integration.
CI as a gate
# .github/workflows/lint.yml
- run: npx eslint . --max-warnings=0
--max-warnings=0 makes any warning a CI failure — useful for staged rollout where new rules start as warnings before promotion to errors.
Cache for fast incremental runs
npx eslint . --cache --cache-location=.eslintcache
Output:
✔ No ESLint warnings or errors (cache: 287/312 files unchanged, 25 re-linted)
.eslintcache is git-ignored; CI can actions/cache@v4 it for cross-run reuse.
Per-PR scope
For huge repos, lint only changed files:
git diff --name-only --diff-filter=ACMR origin/main | grep -E '\.(ts|tsx|js|jsx)$' | xargs npx eslint --max-warnings=0
Output:
src/components/UserCard.tsx
17:9 warning 'user' is defined but never used @typescript-eslint/no-unused-vars
✖ 1 problem (0 errors, 1 warning)
Tradeoff: a config change won't re-validate untouched files. Run the full lint nightly as a backstop.
eslint --fix is not for CI
--fix rewrites files. Never run in CI on someone else's commit — it's a code-injection vector on pull_request_target workflows. Reserve for local + pre-commit.
Performance tuning
ESLint v9 is faster than v8, but type-aware rules dominate runtime on large codebases.
TIMING=1
TIMING=1 npx eslint . | head -30
Output:
Rule | Time (ms) | Relative
:---------------------------------------------|----------:|--------:
@typescript-eslint/no-floating-promises | 1842.301 | 34.2%
@typescript-eslint/no-misused-promises | 1104.118 | 20.5%
import/no-cycle | 712.504 | 13.2%
@typescript-eslint/no-unsafe-assignment | 388.221 | 7.2%
Lists rules sorted by total time. The usual suspects: import/no-cycle, @typescript-eslint/no-floating-promises, @typescript-eslint/no-misused-promises — any rule that walks the type graph.
Scope type-aware rules
Use a separate config for type-aware rules and run it only in CI:
// eslint.config.js (default — fast)
export default [js.configs.recommended];
// eslint.config.ci.js (slow — type-aware)
export default [
...tseslint.configs.recommendedTypeChecked,
{ languageOptions: { parserOptions: { project: "./tsconfig.json" } } },
];
{ "scripts": { "lint": "eslint .", "lint:ci": "eslint --config eslint.config.ci.js ." } }
Parallel via eslint-d or Nx
eslint_d runs ESLint as a long-lived daemon — re-uses parser state across runs, 5-10× faster for repeated small lints (editor on-save).
npm install -g eslint_d
eslint_d .
Output:
added 1 package in 4s
✔ No ESLint warnings or errors (daemon: 18ms)
Nx and Turborepo offer per-package caching that's more sophisticated than --cache.
ESM/CJS interop & bundling
ESLint core is CJS internally but understands ESM configs. The eslint.config.js file:
.js— interpreted perpackage.json"type"field.mjs— always ESM.cjs— always CJS
Plugins can publish ESM, CJS, or both. Flat config imports plugins directly:
import react from "eslint-plugin-react"; // ESM
const react = require("eslint-plugin-react"); // CJS — only in .cjs config
The legacy .eslintrc resolved plugins by name string; flat config resolves by import, which is more robust (no plugin-discovery issues in pnpm).
Version migration guide
| From → To | Highlights |
|---|---|
| 7 → 8 | Node 12+. eslint:recommended rules updated. Plugin authors got a new meta.fixable requirement. |
| 8 → 9 | Flat config mandatory. Legacy .eslintrc.* files ignored unless ESLINT_USE_FLAT_CONFIG=false. Node 18.18+ required. Some core rules removed (formatters moved to @stylistic). Many plugins lagged 6-12 months. |
| typescript-eslint 7 → 8 | Single package (typescript-eslint) replaces @typescript-eslint/parser + @typescript-eslint/eslint-plugin pair for flat config. Legacy two-package form still works for old configs. |
Common 8→9 friction
- Plugins not yet flat-config-native — wrap with
@eslint/compat'sfixupPluginRules():import { fixupPluginRules } from "@eslint/compat"; import legacyPlugin from "eslint-plugin-legacy"; export default [{ plugins: { legacy: fixupPluginRules(legacyPlugin) } }]; .eslintignoreremoved — move to a top-level{ ignores: [...] }config block.extendsremoved — flat config uses spread (...preset) instead.env,globals,parsermoved — all underlanguageOptions.
Security considerations
- Plugin install = arbitrary code. ESLint plugins run with full Node permissions. Audit; pin exact; review changelogs.
eslint-plugin-securityflags common Node security issues (detect-eval-with-expression,detect-non-literal-require). Enable on Node service repos.no-eval/no-implied-eval— core rules; always on.no-restricted-importscan block dangerous packages or modules with security implications:"no-restricted-imports": ["error", { patterns: ["lodash/*"], paths: [{ name: "fs", importNames: ["readFileSync"], message: "Use fs/promises" }] }]- CI execution of
--fixis a code-injection vector onpull_request_target. Only run onpull_request(read-only checkout).
Testing strategies
ESLint itself is a testing strategy — every CI run is a static-analysis test. Beyond that:
eslint --rule '{"my-rule": "error"}'to test a custom rule on the CLI.@typescript-eslint/rule-testerfor custom rule unit tests — provides typed test harness.vitestintegration — runnpx eslint .from a Vitest test to fail CI on lint errors via the test suite.
Configuration patterns
Per-environment globals
{
files: ["src/server/**/*.ts"],
languageOptions: { globals: { ...globals.node } },
},
{
files: ["src/client/**/*.ts"],
languageOptions: { globals: { ...globals.browser } },
},
Override extra strict for one path
{
files: ["src/critical/**/*.ts"],
rules: {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/strict-boolean-expressions": "error",
"no-restricted-syntax": ["error", "ThrowStatement"],
},
}
Project references with parserOptions.project
{
languageOptions: {
parserOptions: {
project: ["./tsconfig.eslint.json"],
tsconfigRootDir: import.meta.dirname,
},
},
}
Use a dedicated tsconfig.eslint.json that extends the main one but "include"s test files and configs — keeps type-aware rules running across everything.
Troubleshooting common errors
Invalid Options: 'extends'— flat config doesn't acceptextends. Use spread:[...js.configs.recommended, { rules: { ... } }].Plugin "foo" was conflicting with another plugin— two configs registered the same plugin key. Use a unique key per plugin instance.Parsing error: '>' expected— TypeScript file isn't picked up by TS parser. Setfiles: ["**/*.{ts,tsx}"]on the TS-specific config block.'X' is not defined (no-undef)in TS code — disableno-undeffor TS files; TypeScript already enforces. The recommended TS configs do this by default.ENOENT: no such file or directoryfor.eslintignore—.eslintignoreis removed in v9. Useignoresin flat config.
Ecosystem integrations
The most-used plugins beyond the install table:
| Plugin | What it covers |
|---|---|
eslint-plugin-react / eslint-plugin-react-hooks | React + Hooks rules (mandatory for React) |
eslint-plugin-vue | Vue 2/3 |
eslint-plugin-svelte | Svelte |
eslint-plugin-astro | Astro |
eslint-plugin-import-x | Import-graph validation (maintained fork of eslint-plugin-import) |
eslint-plugin-unicorn | Opinionated modern-JS rules |
eslint-plugin-jsx-a11y | Accessibility |
eslint-plugin-jest / eslint-plugin-vitest | Test-runner-aware rules |
eslint-plugin-tailwindcss | Class-order, conflicting-classes checks |
eslint-plugin-security | Node security |
eslint-plugin-n | Node-specific (successor to eslint-plugin-node) |
eslint-config-prettier | Disables conflicting stylistic rules |
When NOT to use this
- New small projects. Biome covers ~95% of common ESLint rules in one binary, 25-35× faster. Pick for new projects without a specific plugin need.
- Pure JS-only tooling. Stylelint covers CSS better; markdownlint covers Markdown better. ESLint's strength is JS/TS.
- Editor performance issues. ESLint's editor integration parses on every keystroke for some rules. If editor lag is intolerable, switch to
eslint_dor limit ESLint to type-fast rules in the editor and run the slow ones in CI.
See also
- JavaScript: eslint — flat-config recipes, rule sets
- JavaScript: prettier — pair via
eslint-config-prettier - JavaScript: biome — single-binary alternative
- Packages: npm-prettier
- Packages: npm-biome