eslint
ESLint is the standard linter for JavaScript: it parses your code into an AST with Espree and reports patterns that are likely bugs or that violate your team's conventions. Every rule is a plugin, so the same tool that flags an unused variable can also enforce React hooks rules or import ordering once you add the right packages. It runs from the CLI, in editors, and in CI, and many rules can rewrite the offending code for you with --fix.
Still the default JavaScript linter and the only one with a truly deep rule ecosystem, so most teams should just use it. Go in expecting config migration churn between majors and slower runs than the Rust competition, especially with type-aware rules.
Use it if
- You maintain a team JavaScript or TypeScript codebase and want unused variables, unreachable code, and footguns caught in CI before review
- You need the plugin ecosystem: React hooks rules, import ordering, accessibility checks, or your own custom rules on top of the core
- You are cleaning up a legacy codebase and want autofix (--fix) to do the mechanical work across thousands of files
- You want editor integration that virtually every IDE already ships or supports
- You want code formatting: ESLint is a linter, not a formatter, and its own README points you to Prettier for that job
- Lint time is your bottleneck on a large repo: Rust-based tools like oxlint and Biome run the common rules far faster, and ESLint with type-aware TypeScript rules gets genuinely slow
- Your config and plugins are still on legacy .eslintrc: current majors are flat-config (eslint.config.js), and dragging old shared configs through the migration is real work
- You cannot pin versions tightly: ESLint's own semver policy says minor releases may report new errors and break your lint build, which is why the project recommends tilde ranges
Setup reality
npm init @eslint/config@latest scaffolds a working eslint.config.js, and for plain JavaScript that is genuinely it. The friction shows up around the edges: you need Node ^20.19.0, ^22.13.0, or >=24; TypeScript support means adding typescript-eslint and, for type-aware rules, wiring in your tsconfig and accepting slower runs; pnpm users are told to set auto-install-peers and hoisted node-linker in .npmrc to avoid resolution errors; and any plugin that has not migrated to flat config needs a compat shim. Budget the first afternoon for config, not for reading rule docs.
Patterns
Set up ESLint in a new projectinit-project
npm init @eslint/config@latest
# answers a few questions, installs deps,
# and writes eslint.config.js
npx eslint .This scaffolds flat config; do not copy an old .eslintrc from another project, current majors will not read it.
Minimal flat config with rulesbasic-config
// eslint.config.js
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js", "**/*.cjs", "**/*.mjs"],
rules: {
"prefer-const": "warn",
"no-constant-binary-expression": "error",
},
},
]);Severity is "off", "warn", or "error"; only "error" makes the CLI exit non-zero, so CI ignores warnings unless you use --max-warnings.
Start from the recommended rule setrecommended-rules
// eslint.config.js
import js from "@eslint/js";
import { defineConfig } from "eslint/config";
export default defineConfig([
js.configs.recommended,
{
rules: {
"no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
},
},
]);@eslint/js is a separate package now; the old "eslint:recommended" string form belongs to legacy config.
Lint TypeScript with typescript-eslintlint-typescript
// eslint.config.js
import tseslint from "typescript-eslint";
export default tseslint.config(
...tseslint.configs.recommended,
{
rules: {
"@typescript-eslint/no-explicit-any": "warn",
},
},
);recommended does not type-check; switch to recommendedTypeChecked plus parserOptions.projectService for type-aware rules, at a real speed cost.
Ignore build output and vendored codeignore-files
// eslint.config.js
import { defineConfig, globalIgnores } from "eslint/config";
export default defineConfig([
globalIgnores(["dist/", "coverage/", "**/*.min.js"]),
// ...your other config objects
]);An ignores key next to files only scopes that one config object; use globalIgnores to exclude files from linting entirely.
Declare browser or Node globalsdefine-globals
// eslint.config.js
import globals from "globals";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.js"],
languageOptions: {
globals: { ...globals.browser, ...globals.node },
},
},
]);Without this, no-undef flags window, document, and process; the globals package is a separate install.
Add a plugin (React example)use-plugin
// eslint.config.js
import react from "eslint-plugin-react";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["**/*.{js,jsx}"],
plugins: { react },
rules: {
"react/jsx-key": "error",
},
settings: { react: { version: "detect" } },
},
]);ESLint parses JSX syntax natively but knows nothing about React semantics; that is exactly what eslint-plugin-react adds.
Auto-fix everything fixableautofix
npx eslint . --fix
# preview without writing:
npx eslint . --fix-dry-runOnly rules marked fixable are rewritten; the rest still print as problems, so a clean --fix run does not mean a clean lint.
Suppress a rule for one linedisable-rule-inline
// eslint-disable-next-line no-console -- CLI output is intentional
console.log(report);Always name the rule; a bare eslint-disable-next-line silences everything on that line and hides future bugs.
Fail CI on any warningci-strict
npx eslint . --max-warnings 0
# machine-readable output for tooling:
npx eslint . --format json -o eslint-report.jsonExit code is 1 only for errors by default; --max-warnings 0 is how you make warn-level rules block a pipeline.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @biomejs/biome | npm | You want linting and formatting in one fast Rust binary and can live with a smaller rule ecosystem. |
| oxlint | npm | You want a drop-in speed boost for the common correctness rules on a big repo, possibly running in front of ESLint. |
| standard | npm | You want zero configuration decisions: it is ESLint underneath with a fixed, non-negotiable rule set. |