What is ESLint?
ESLint is a pluggable linter for JavaScript and TypeScript. It parses your code into a syntax tree, runs a set of rules against it and reports problems. Rules range from genuine bug detection — unused variables, missing await, unsafe equality — to conventions your team wants to enforce.
The value is cumulative. Each rule catches a class of mistake once, everywhere, forever, instead of relying on reviewers to notice it. Many rules also fix themselves, so eslint --fix cleans up a surprising amount of code automatically. ESLint is one of the highest-leverage tools in a JavaScript project.
Flat config
ESLint uses flat config: a file that exports an array of config objects. It replaces the older .eslintrc format with explicit imports and predictable composition.
// eslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
{ ignores: ["dist", "coverage", "**/*.generated.*"] },
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.ts", "**/*.tsx"],
rules: {
"@typescript-eslint/no-floating-promises": "error",
},
},
);
Each object can specify which files it applies to, which plugins it registers and which rules it sets. Later objects override earlier ones, so the array reads top to bottom like a cascade.
Rules and severity
Every rule has a severity: "off", "warn" or "error".
// rules.js
export default [
{
rules: {
eqeqeq: "error", // require ===
"no-unused-vars": "warn",
"no-console": ["warn", { allow: ["warn", "error"] }],
},
},
];
Some rules take options, as no-console does here. error fails CI; warn reports without failing. Use warn for rules you are rolling out gradually and error for anything that should block a merge.
Plugins and presets
Plugins add rules for specific ecosystems, and presets package a curated set of them.
// react.js
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
export default [
react.configs.flat.recommended,
reactHooks.configs["recommended-latest"],
];
Common plugins cover React, Vue, TypeScript, imports, testing libraries and accessibility. Starting from eslint:recommended plus a framework preset gets you 90% of the value with almost no configuration. Add individual rules only when you have a reason.
Type-aware linting
typescript-eslint can use the TypeScript type checker for rules that plain syntax analysis cannot handle.
// type-aware.js
export default tseslint.config({
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
},
});
Rules like no-floating-promises catch bugs that are genuinely hard to spot by reading: a promise that is never awaited. Type-aware linting is slower because it runs the type checker, so many projects enable it for source code and skip it for tests and config files.
Fixing and disabling
Many rules are auto-fixable, so run the fixer before reading the output.
npx eslint . --fix
When you genuinely need to break a rule, disable it narrowly and explain why.
// targeted.js
// eslint-disable-next-line no-console -- intentional debug log
console.log("payment flow", payload);
Blanket /* eslint-disable */ at the top of a file turns off everything, including the rules that would have caught real bugs. Prefer a single-line disable with a reason, and adjust the config if a rule conflicts with your standards.
Editors and CI
Install the ESLint extension for your editor and enable fix-on-save. You get feedback as you type and safe fixes applied immediately, which makes linting part of the editing loop rather than a chore. In CI, run ESLint as a separate step so failures are clear and fast, and cache the results where possible.
Where ESLint fits
ESLint handles code quality; Prettier handles formatting. The standard setup is to let Prettier own style and disable the ESLint rules that conflict, often with a shared config. Together they mean reviewers discuss design instead of spacing and semicolons.
Best practices
- Start from
eslint:recommendedand a framework preset, then add rules deliberately. - Use flat config with explicit imports and a clear ignores list.
- Let Prettier own formatting and disable conflicting ESLint rules.
- Run
--fixbefore triaging remaining problems. - Enable type-aware rules for source files.
- Disable rules narrowly, with a comment explaining why.
- Lint in the editor, in a pre-commit hook and in CI.
Common mistakes
- Linting
dist, generated code and dependencies. - Blanket-disabling rules instead of fixing the root cause.
- Enabling every rule at once and drowning in noise.
- Duplicating formatting rules that Prettier already handles.
- Running type-aware linting on the whole repo and making it slow.
- Treating warnings as harmless until they pile up.
Where to go next
ESLint is the quality gate for your codebase. Pair it with Prettier for formatting, deepen the types with TypeScript, and wire it into the Vite build and your CI pipeline. Then turn on one new rule and fix every instance — it is a small habit with a large payoff.