TypeScript

Set Up typescript-eslint: Start with Syntax Rules, Add Type-Aware Linting When It Pays

Configure typescript-eslint flat config for JS and TS, avoid duplicate unused-variable reports, and opt into typed rules for TypeScript files.

Editorial illustration for Set Up typescript-eslint: Start with Syntax Rules, Add Type-Aware Linting When It Pays

Start with ESLint’s and typescript-eslint’s recommended rules. That gives you a useful baseline without making every lint run depend on project type information. Add typed rules when you have a check you want—such as finding unhandled Promises—and scope that work to TypeScript files.

Install the flat-config tooling

In a project that already uses TypeScript, install the lint tooling as development dependencies:

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

The examples below use eslint.config.mjs and defineConfig from eslint/config. That defineConfig API requires ESLint 9.22.0 or later; check your installed ESLint version if the import does not work. The typescript-eslint package exposes the shared configs, parser, and plugin used here. Its package documentation shows the same defineConfig approach.

Start with recommended rules

Create eslint.config.mjs:

import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';

export default defineConfig(
  {
    files: ['**/*.{js,ts}'],
    extends: [js.configs.recommended, tseslint.configs.recommended],
  },
  {
    files: ['**/*.ts'],
    rules: {
      'no-unused-vars': 'off',
      '@typescript-eslint/no-unused-vars': [
        'error',
        { varsIgnorePattern: '^_', argsIgnorePattern: '^_' },
      ],
    },
  },
);

The first block limits these rules to JavaScript and TypeScript files. The shared typescript-eslint recommended config supplies its parser and plugin; you do not need to declare them again for this baseline. It is a starting set of recommended rules, not the opt-in type-checked set.

The second block makes the unused-variable choice explicit for TypeScript. Core no-unused-vars can report incorrectly on TypeScript syntax, so turn it off where you use @typescript-eslint/no-unused-vars. The example’s patterns permit underscore-prefixed variables and arguments; remove those options if your project wants to report them instead. ESLint’s discussion of unused-variable checks also recommends leaving TypeScript’s noUnusedLocals and noUnusedParameters off when linting handles this job: lint rules offer finer control over project preferences.

Add typed rules when you have a use for them

If you want rules that consult TypeScript type information, add a third block to the same defineConfig call, after the two blocks above and before its closing );:

{
  files: ['**/*.ts'],
  extends: [
    tseslint.configs.recommendedTypeCheckedOnly,
    {
      languageOptions: {
        parserOptions: {
          projectService: true,
        },
      },
    },
  ],
},

This retains the original recommended rules and adds the recommended type-checked-only rules for .ts files. The typed-config example uses recommendedTypeCheckedOnly with projectService: true. A concrete rule in that set is @typescript-eslint/no-floating-promises, which checks whether Promise-like statements are handled appropriately. Typed rules can use information from other project files, but that comes at a cost: linting can be roughly as slow as type-checking. If the additional reports are not useful to your project, keep the simpler config.

In this mixed JS/TS setup, the typed block matches only .ts files. There is no reason to apply it to JavaScript and then undo it. If you later choose a broader typed block that also matches .js, put this override after that block to exclude JavaScript from type-aware linting:

{
  files: ['**/*.js'],
  extends: [tseslint.configs.disableTypeChecked],
},

That is the documented mixed-project pattern; it is not needed with the TypeScript-only block above.

Check what is actually running

For a project whose files are under src, run ESLint against both extensions:

npx eslint "src/**/*.{js,ts}"

Confirm that the config loads and that an ordinary recommended-rule violation in an intended file is reported. For an unused declaration in a .ts file, inspect the reported rule name: it should be @typescript-eslint/no-unused-vars, not a second report from core no-unused-vars.

Then, with the typed block enabled, try this deliberately unhandled Promise in a .ts file included in the project:

async function send(): Promise<void> {
  // Example operation.
}

send(); // Deliberately unhandled for the lint check.

A report from @typescript-eslint/no-floating-promises establishes that the type-aware rule is active for that file. It does not establish that the operation succeeds, that failures are handled at runtime, or that the application is correct. That distinction is a useful reason to check the rule name rather than treating a passing lint command as a runtime test.

Find a note

Search by topic, title, or keyword.