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.

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.
