Code Quality

ESLint

ESLint is the pluggable linter that catches bugs and enforces conventions before they reach review. With flat config and TypeScript support, it fits any modern stack.

intermediate13 min readUpdated Sep 15, 2026
eslint.config.js
js
// eslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";

export default tseslint.config(
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    rules: {
      "no-console": ["warn", { allow: ["warn", "error"] }],
      "no-unused-vars": "off",
      "@typescript-eslint/no-unused-vars": "error",
    },
  },
);
Parses
JavaScript and TypeScript
Config
eslint.config.js (flat)
Rules
Off, warn, error
Fixes
Many rules auto-fix
Extensible
Plugins and parsers
Runs in
CLI, editors, CI

Why it matters

Why linting pays for itself

Catch bugs early

Rules detect unused variables, unsafe equality, missing awaits and hundreds of other mistakes before they ship.

Automatic fixes

Many problems are fixed with --fix, so style and simple bugs are corrected without manual editing.

Enforce conventions

Teams codify their standards in config so reviews focus on design rather than nitpicks.

The big picture

The three ideas behind ESLint

A parser reads your code, a set of rules reports problems, and config decides which rules apply where.

The parser

Read

Turns source code into an AST that rules can inspect, including TypeScript through a parser.

Rules

Report

Individual checks, each configurable as off, warn or error, and often auto-fixable.

Config

Compose

Flat config files combine recommended presets, plugins and per-file overrides.

ESLint at a glance

The core of ESLint

eslint.config.js

The flat config file that exports an array of config objects.

Rule severity

off, warn and error control how each rule reports.

Plugins

Add rules for React, TypeScript, imports, accessibility and more.

Presets

Shareable configs package a curated set of rules.

--fix

Apply automatic fixes for the rules that support them.

Editors and CI

Lint on save locally and fail the build in CI.

A short history

From JSHint successor to the linting standard

  1. 2013

    ESLint released

    Nicholas Zakas creates a fully pluggable linter as a successor to JSHint.

    13
  2. 2015

    ES6 and JSX support

    Modern syntax and React support make ESLint the default choice.

    15
  3. 2019

    TypeScript first-class

    typescript-eslint brings type-aware linting to the ecosystem.

    19
  4. 2024

    Flat config default

    The flat config format becomes the standard, replacing .eslintrc files.

    24
  5. Today

    The linting standard

    Used by nearly every JavaScript and TypeScript project.

    Today

The complete guide

ESLint: Everything you need to know

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:recommended and 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 --fix before 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.

Disabling a rule

Change the config or add a targeted disable with a reason. Blanket file-level disables hide real problems.

Prefer
// eslint-disable-next-line no-console
console.log("debugging payment flow");
Avoid
/* eslint-disable */
// the whole file is now
// unchecked, including
// real mistakes

Using flat config

Flat config is the current standard — a plain array of config objects, easier to compose and debug.

Prefer
export default [
  js.configs.recommended,
  {
    files: ["**/*.ts"],
    rules: { semi: "error" },
  },
];
Avoid
// legacy .eslintrc.json with
// extends, overrides and
// implicit plugin resolution

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning ESLint?

Our interactive tutorial walks you through ESLint step by step — with quizzes and real code you can run in the browser.