Set Up ESLint for TypeScript

Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.

6 min read

ESLint is a static analysis tool that reads your code and reports problems without running it. The TypeScript compiler catches type errors. ESLint catches a different category of bugs: unused variables, suspicious equality checks, missing await on promises, and patterns that are technically valid but likely wrong.

With the typescript-eslint plugin, ESLint understands TypeScript syntax. It flags issues like using the any type unnecessarily or forgetting to handle promise rejections. For code formatting, pair ESLint with a formatter like Prettier. See Set Up Prettier for TypeScript for that setup.

Install the Packages

Start with a TypeScript project that already has a package.json and tsconfig.json. Install the four required packages:

bashbash
npm install --save-dev eslint @eslint/js typescript typescript-eslint

The core eslint package is the linter itself. The @eslint/js package provides built-in recommended rules for JavaScript. The typescript package gives the plugin access to the compiler API. And typescript-eslint is the bridge that lets ESLint parse and lint .ts files directly.

Create the Flat Config

ESLint v9 and later use a flat config format in an eslint.config.mjs file. It is simpler than the legacy .eslintrc format and is the only format typescript-eslint recommends for new projects.

Create eslint.config.mjs at the root of your project:

typescripttypescript
// @ts-check
import js from "@eslint/js";
import tseslint from "typescript-eslint";
 
export default tseslint.config(
  {
    files: ["**/*.{ts,js}"],
    extends: [js.configs.recommended, tseslint.configs.recommended],
  }
);

The // @ts-check comment lets TypeScript check the config file itself for mistakes. The files field scopes rules to TypeScript and JavaScript files.

The js.configs.recommended preset enables core rules like banning debugger statements and duplicate imports. The tseslint.configs.recommended preset adds TypeScript-specific rules that flag unnecessary any usage and catch misused promises.

Add a Lint Script

Add a script to your package.json so you can run ESLint with a single command:

jsonjson
{
  "scripts": {
    "lint": "eslint ."
  }
}

This adds a lint entry to your npm scripts. Now run it from your terminal to see ESLint in action:

bashbash
npm run lint

When you run it, ESLint scans every TypeScript and JavaScript file and reports any problems. If the code is clean, it prints nothing and exits with code 0.

When a problem is found, the output pinpoints the exact file, line number, and rule that triggered:

texttext
src/utils.ts
  3:7  error  'unusedVar' is assigned a value but never used
              @typescript-eslint/no-unused-vars

The error message tells you exactly what is wrong and which rule to look up for an explanation.

Add Stricter Rules

The recommended config is a safe starting point. As your project grows, you will likely want stricter checks. The typescript-eslint plugin provides two additional config layers.

The strict config catches subtler bugs like non-exhaustive checks on discriminated unions. The stylistic config enforces consistent code style: spacing, semicolons, and member ordering. These style rules do not catch bugs, but they keep the codebase looking identical across every contributor.

Add both by extending them in the config:

typescripttypescript
// @ts-check
import js from "@eslint/js";
import tseslint from "typescript-eslint";
 
export default tseslint.config({
  files: ["**/*.{ts,js}"],
  extends: [
    js.configs.recommended,
    tseslint.configs.recommended,
    tseslint.configs.strict,
    tseslint.configs.stylistic,
  ],
});

These extra configs are opinionated. If a specific rule does not fit your team, turn it off individually under the rules key in the config object.

Ignore Generated Files

Build output, generated code, and third-party bundles should never be linted. Add an ignores entry to skip them:

typescripttypescript
// @ts-check
import js from "@eslint/js";
import tseslint from "typescript-eslint";
 
export default tseslint.config(
  {
    ignores: ["dist/", "coverage/"],
  },
  {
    files: ["**/*.{ts,js}"],
    extends: [js.configs.recommended, tseslint.configs.recommended],
  }
);

The ignores array uses glob patterns. ESLint automatically skips the node_modules directory by default, so you do not need to list it here.

Run ESLint in CI

Linting is most valuable when it runs on every push and blocks merging if there are violations. In your CI workflow, use the same command with one extra flag:

bashbash
npx eslint . --max-warnings 0

The --max-warnings 0 flag treats warnings as errors, so any warning fails the build. This prevents warnings from piling up over time.

Typed Linting for Deeper Checks

Typed linting uses the TypeScript compiler's type information to run rules that need full type knowledge. It can detect when you call a function that returns a promise but forget to await it, or when you access a property that might be undefined.

Typed linting requires adding a project field to the config pointing to your tsconfig.json. It is powerful but noticeably slower because ESLint must run the full TypeScript compiler on every file.

Start without typed linting. Once your project has a stable type-checking setup and you want deeper scrutiny, add it. For understanding how the TypeScript compiler fits into your overall toolchain, see TypeScript compiler explained.

Rune AI

Rune AI

Key Insights

  • Install eslint, @eslint/js, typescript, and typescript-eslint as dev dependencies.
  • Use the flat config format with eslint.config.mjs, not the legacy .eslintrc.
  • Enable tseslint.configs.recommended for a solid set of TypeScript-aware rules.
  • Add a lint script to package.json and run it in CI to prevent regressions.
  • Typed linting is powerful but optional; start without it and add it later.
RunePowered by Rune AI

Frequently Asked Questions

Do I need ESLint if I already have the TypeScript compiler?

Yes. The TypeScript compiler checks types. ESLint checks code style, best practices, and catches logic bugs that are not type errors. They catch different categories of problems and work best together.

What is the difference between eslint.config.mjs and .eslintrc?

eslint.config.mjs is the new flat config format introduced in ESLint v9. .eslintrc is the legacy format. The flat config is simpler, faster, and the only format typescript-eslint recommends for new projects.

Should I use typed linting from the start?

Start with the recommended config first. Typed linting uses TypeScript's type information to catch deeper bugs, but it is slower to run. Add it once your project grows and you want stricter rules.

Conclusion

ESLint with typescript-eslint gives you a safety net that catches bugs the TypeScript compiler cannot. Install three packages, create one config file, and you have linting that understands TypeScript. Start with the recommended config and add stricter rules as your project grows.