A maintainable TypeScript ESLint setup should catch real defects, fit the project’s build workflow, and remain understandable as the codebase changes. This guide provides a practical flat-config example, scenario-based checklists for React, Next.js, Node.js, and existing JavaScript projects, plus guidance for type-aware rules, formatting, CI, editor integration, and future reviews.
Overview
ESLint examines source code for patterns that are likely to cause bugs, reduce readability, or make maintenance harder. TypeScript adds another layer: some useful checks require the TypeScript compiler to understand the project’s types rather than treating every file as plain JavaScript.
A good TypeScript ESLint configuration separates three concerns:
- Syntax and general code quality: rules that can inspect code without consulting the TypeScript type checker.
- Type-aware analysis: rules that can identify unsafe assignments, floating promises, unnecessary conditions, and related issues.
- Formatting: whitespace and layout decisions, usually handled by a dedicated formatter rather than ESLint.
Modern ESLint projects generally use a flat configuration file such as eslint.config.mjs. The exact packages and available presets can change, so treat the following as a starting pattern and confirm the configuration shape supported by the versions installed in your repository.
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.ts", "**/*.tsx"],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname
}
},
rules: {
"no-console": "warn",
"@typescript-eslint/consistent-type-imports": "error"
}
},
{
ignores: ["dist/**", "coverage/**", ".next/**"]
}
);The configuration should match the project’s actual source boundaries. A library, application, monorepo, and migration project may need different file globs, TypeScript project settings, and rule severity. For background on how compiler settings affect linting and builds, see the TypeScript tsconfig guide.
Checklist by scenario
Starting a new TypeScript project
- Install ESLint, the TypeScript ESLint integration, and the shared JavaScript recommended rules as development dependencies.
- Create
eslint.config.mjsat the repository root. - Apply general recommended rules, then TypeScript recommended rules.
- Define the files that should be linted instead of assuming every repository file is TypeScript source.
- Ignore generated output, dependency directories, coverage reports, build caches, and framework-specific output.
- Add a package script such as
lintand make it run against the same source directories used by the build. - Run linting before adding many custom rules. The initial goal is a predictable baseline, not maximum strictness.
Adding ESLint to an existing JavaScript or TypeScript codebase
- Run ESLint without changing rule severity and record the current error count.
- Separate existing violations from newly introduced violations. A temporary baseline or narrowly scoped file patterns can help teams migrate without hiding new problems.
- Start with rules that prevent likely defects, then address stylistic disagreements through a formatter or documented conventions.
- Use directory-specific overrides when legacy code, generated files, tests, or scripts have different requirements.
- Convert warnings into errors gradually, preferably when a file is edited or a module is migrated.
If the project is also moving from JavaScript to TypeScript, keep migration work separate from broad lint cleanup where possible. The JavaScript to TypeScript migration checklist can help you sequence compiler, typing, and workflow changes.
Using type-aware linting
Type-aware rules can provide stronger feedback, but they need access to the correct TypeScript project. Enable them only after ordinary linting works reliably.
- Use a type-checked preset supported by your installed TypeScript ESLint version.
- Configure project service or an equivalent project lookup method according to the integration’s documentation.
- Ensure every linted TypeScript file belongs to an appropriate
tsconfig. - Keep generated files and unrelated scripts outside the type-aware file set when they do not belong to the application’s TypeScript project.
- Expect slower lint runs than syntax-only checks and decide whether developers need a fast local command and a deeper CI command.
Useful type-aware checks often concern unsafe values, promises, assertions, and conditions. Enable them as a group only when the team understands the remediation path; otherwise, choose a smaller set and document exceptions.
TypeScript with React or Next.js
- Include
.tsxfiles in the TypeScript file patterns. - Add the React-focused ESLint integration if the project needs rules for hooks, component patterns, or JSX accessibility.
- Confirm that framework-generated directories such as
.nextand build output are ignored. - Check that server and client files are covered by the intended configuration, especially when the framework uses separate runtimes.
- Keep rules for hooks and browser globals scoped to the files where they apply.
For a broader application setup, compare the lint configuration with the project boundaries described in this TypeScript with Next.js guide.
Node.js services and libraries
- Define whether source files use ESM, CommonJS, or a mixed transition, and make the ESLint configuration consistent with that choice.
- Configure Node.js globals and runtime assumptions without applying browser-only rules.
- Lint tests, scripts, and source code with separate overrides when their APIs differ.
- For libraries, lint declaration-adjacent source and check that build output is excluded unless generated output is intentionally inspected.
- Pair linting with the project’s type-check and test commands rather than treating lint as a replacement for either.
What to double-check
Rules and severity
Use error for violations that should block a merge, warn for useful guidance that is not yet consistently actionable, and disabled rules for intentional exceptions. Avoid enabling overlapping rules from both the base JavaScript configuration and TypeScript-specific replacements when the TypeScript integration provides a safer equivalent.
Rules should answer a concrete maintenance need. For example, consistent type imports can make module intent clearer, while promise-related rules can expose asynchronous work that is accidentally ignored. A long list of stylistic rules is less valuable if developers routinely disable it or format code in a conflicting tool.
Formatting boundaries
Decide which tool owns formatting. If a formatter handles layout, avoid duplicating its decisions with a large set of ESLint style rules. ESLint should focus primarily on correctness, maintainability, and project conventions. Run formatting separately or use an editor integration that applies both tools in a predictable order.
Ignore patterns and file ownership
Check the lint target with an intentionally broad command and inspect which files are included. An overly broad ignore can conceal source files; an overly narrow ignore can lint generated code and create noisy failures. Review output directories after changing build tools, framework versions, or the TypeScript project structure.
CI and local commands
Use the same configuration in the editor, local scripts, pre-commit workflow, and CI. A practical arrangement is a fast lint command for changed work and a repository-wide CI command. CI should also run the TypeScript compiler in no-emit or equivalent type-check mode, because ESLint and the compiler answer different questions.
Document commands for installing dependencies, linting one file, fixing safe violations, running the full check, and diagnosing type-aware configuration errors. Clear commands reduce the temptation to bypass the checks.
Common mistakes
- Using an outdated configuration model: flat config and legacy configuration files are not interchangeable. Keep one deliberate configuration path for the repository.
- Enabling type-aware rules before project discovery works: errors about files not being included usually indicate a mismatch between lint globs and
tsconfigboundaries. - Linting build output: generated files create duplicate errors and distract from source problems. Ignore them unless inspecting generated code is an explicit requirement.
- Making every warning an error immediately: this can turn a useful migration into a large, low-value cleanup project. Stage adoption and protect new code first.
- Using
anyas the default escape hatch: if an external API is uncertain, consider a typed boundary and runtime validation. The runtime validation guide explains how schemas can protect data entering a TypeScript application. - Assuming linting replaces tests or type-checking: lint rules find patterns; the compiler checks static type relationships; tests verify behavior. Keep all three where they provide distinct value.
- Adding exceptions without explanations: inline disables should be narrow and include a reason when the exception may surprise a future maintainer.
When to revisit
Review the TypeScript ESLint setup before a seasonal planning cycle, during dependency upgrades, and whenever the project’s workflow changes. Revisit it after moving to a new build tool, introducing a monorepo, changing module systems, adding a framework, or separating browser and server packages.
Use this short review checklist:
- Confirm that the installed ESLint and TypeScript ESLint versions support the configuration style and presets in use.
- Run linting against representative source, test, script, and generated-file paths.
- Check whether new framework directories or build outputs need ignore patterns.
- Review warnings that have remained unresolved and decide whether to fix, document, narrow, or remove them.
- Measure whether type-aware linting remains useful enough to justify its runtime and configuration cost.
- Compare lint rules with current TypeScript compiler settings, project boundaries, and runtime validation practices.
- Update editor and CI instructions so a new contributor can reproduce the same result locally.
The best TypeScript ESLint configuration is not the strictest possible configuration. It is the one that consistently catches meaningful problems, gives developers an understandable path to resolution, and changes deliberately as the codebase evolves. Keep this checklist with the repository’s tooling documentation, and use it whenever the project’s source layout or development workflow changes.