Modern ESLint flat config is a better fit for TypeScript projects than the older layered config model, but the setup details still change depending on whether you are linting an app, a library, a monorepo, or a mixed JavaScript and TypeScript codebase. This guide gives you a reusable checklist you can return to whenever your tooling changes: what to install, how to structure eslint.config.js, when to enable type-aware rules, what to exclude, and which mistakes cause the most wasted time.
Overview
If you want an eslint typescript flat config setup that stays maintainable, the goal is not to turn on every rule. The goal is to create a linting system that matches the shape of your codebase and gives fast, trustworthy feedback.
Flat config changes how ESLint is composed. Instead of inheriting behavior from multiple nested config files, you export an array of configuration objects. That makes overrides more explicit, but it also means you need to be deliberate about file matching, parser setup, globals, and when TypeScript-aware lint rules should run.
A practical typescript eslint config usually has four moving parts:
- Base JavaScript rules for files ESLint can inspect without TypeScript type information.
- TypeScript parsing for
.tsand.tsxfiles. - Optional type-aware rules for stricter checks that rely on your
tsconfig. - Targeted overrides for tests, build scripts, config files, generated output, and framework-specific conventions.
Before you write any config, decide these three things:
- Is this an app, a library, or both? Apps often need framework and test overrides. Libraries often need stricter public API discipline.
- Do you want type-aware linting everywhere? It can catch more issues, but it can also slow down lint runs if applied too broadly.
- Will ESLint be your style formatter? In many teams, ESLint handles code quality while a formatter handles code style. Keeping those concerns separate usually makes the config easier to maintain.
As a baseline, your lint setup should answer these questions clearly:
- Which files are linted?
- Which files are ignored?
- Which rules apply to plain JavaScript?
- Which rules apply only to TypeScript?
- Which rules require type information from
tsconfig? - Which directories are intentionally exempt, such as generated code or build output?
If you are still tuning your compiler settings, pair this article with tsconfig.json Best Settings by Project Type. Many ESLint problems are really tsconfig scope problems in disguise.
Checklist by scenario
Use the scenario that matches your project closest. The point is not to copy a single universal config. It is to avoid missing the one or two decisions that matter most for your setup.
Scenario 1: Small app with TypeScript only
This is the cleanest starting point for a modern typescript lint setup.
Checklist:
- Install ESLint, TypeScript, the TypeScript ESLint parser, and the TypeScript ESLint plugin package that supports flat config.
- Create a single
eslint.config.jsoreslint.config.mjsat the project root. - Add a base config object for ignored paths like
dist,build, coverage output, and generated files. - Add a TypeScript-specific config object targeting
**/*.tsand**/*.tsx. - Set the TypeScript parser in that object.
- Enable recommended TypeScript rules first before adding stricter custom rules.
- Decide whether type-aware rules are worth the cost for your project size.
- Run ESLint from the root so file matching behaves consistently in local and CI environments.
Good default mindset: start with recommended rules, then add only the few project-specific rules that improve signal.
Scenario 2: React app with TypeScript
Frontend projects often need more override logic because browser globals, JSX, test runners, and framework files all pull in different assumptions.
Checklist:
- Target both
.tsand.tsxfiles explicitly. - Make sure JSX syntax is supported in your parser settings where needed.
- Separate browser application files from Node-based config files such as bundler or test config scripts.
- Add overrides for test files so test globals and looser patterns do not leak into production code.
- Treat generated route types, framework build artifacts, and codegen output as ignored unless you intentionally lint them.
- Review import path rules if your project uses path aliases from
tsconfig.
If you are setting up a frontend project from scratch, React + TypeScript Setup Guide for Vite, Next.js, and CRA Alternatives is a useful companion.
Scenario 3: Node.js or backend TypeScript project
Backend projects usually have fewer JSX concerns but more environment and module-format questions.
Checklist:
- Confirm whether the project uses ESM, CommonJS, or a mixed setup.
- Apply Node-specific globals only where appropriate.
- Separate runtime source files from tooling files like migration scripts, build scripts, and CLI utilities.
- Decide whether scripts in
scripts/should follow the same strictness as production code. - Use type-aware rules for source code first, not necessarily for every one-off script.
- Keep generated clients, ORM output, and temporary migration artifacts out of the lint path when possible.
For project bootstrapping details beyond linting, see Node.js + TypeScript Setup That Still Works in 2026.
Scenario 4: Library or package meant for reuse
Libraries benefit from stricter rules because unclear exports and unsafe types become downstream problems for users.
Checklist:
- Enable a stricter TypeScript rule set than you would for an internal app.
- Pay attention to rules around explicit exports, unused variables, and accidental
anyusage. - Lint declaration-adjacent patterns carefully if you expose utility types, generics, or public interfaces.
- Keep tests and examples in separate override blocks so demo code does not weaken package code.
- Review package entry points and file globs to avoid linting built output or published artifacts.
Rule of thumb: libraries should optimize for predictability, even if the lint config is a little stricter than an application team might prefer.
Scenario 5: Monorepo with mixed packages
This is where flat config can be especially useful because explicit array-based composition scales well, but only if you stay disciplined.
Checklist:
- Keep one root config if the rules are mostly shared.
- Use separate config objects for package patterns such as
packages/*/src/**/*.ts. - Apply package-specific overrides only when a real difference exists, such as frontend versus backend runtime assumptions.
- Be careful with
tsconfigreferences and type-aware linting so each package resolves types correctly. - Ignore generated package output and cached build directories at the root.
- Make sure CI runs lint from a predictable root directory.
In monorepos, many apparent ESLint errors are really path-resolution or project-boundary issues. Keep your file globs narrow and intentional.
Scenario 6: JavaScript to TypeScript migration
During migration, a rigid all-at-once config tends to create noise. A staged approach works better.
Checklist:
- Lint JavaScript and TypeScript separately instead of forcing identical rules on day one.
- Apply TypeScript parser settings only to TypeScript files.
- Let JavaScript files keep a lighter ruleset while the migration is active.
- Add stricter TypeScript rules gradually as more code becomes typed.
- Use ignores for generated transitional files or vendor folders.
- Document which directories are considered migration zones versus fully typed zones.
This is especially important if your team is still working through common errors like missing globals or unresolved names. For those cases, see How to Fix "Cannot find name" and Other Missing Type Errors in TypeScript and TypeScript Error Codes List: Meaning, Common Causes, and Fixes.
A simple flat config shape to aim for
You do not need a huge file. Most healthy configs follow a structure like this:
- Ignore block for build artifacts and generated files.
- Base JavaScript block for generic linting.
- TypeScript block for
.tsand.tsxfiles. - Type-aware TypeScript block if you choose to enable rules that require
tsconfig. - Overrides for tests, scripts, config files, or framework directories.
That shape is easier to maintain than a single oversized block that tries to handle every case at once.
What to double-check
Most broken eslint flat config typescript setups fail because of a small mismatch, not because the whole approach is wrong. Before assuming a plugin bug, check the basics below.
1. File globs are matching the files you think they are
If your rules do not seem to apply, inspect the files patterns in each config object. A config that targets **/*.ts will not affect .tsx. A config aimed at src/**/*.ts will not touch tools or test folders. Keep patterns explicit.
2. Ignore patterns are not too broad
It is easy to accidentally ignore files you meant to lint, especially in monorepos. Review ignore entries for dist, coverage, generated folders, and framework output. Avoid blanket ignores that hide source files.
3. Type-aware rules can actually see the right tsconfig
If you enable rules that require type information, ESLint needs access to the correct TypeScript project context. When this is wrong, you may see slow runs, parser errors, or confusing unresolved-type issues. Confirm that your eslint tsconfig relationship is intentional: the linted files should belong to the TypeScript project you expect.
4. Node files and browser files are not sharing the same assumptions
Config files, CLI scripts, test setup files, and app source often live in the same repo but need different globals and runtime expectations. Split them into separate config objects instead of forcing one environment across all files.
5. Generated code is handled consistently
If the file is generated, decide one of two things: either ignore it completely, or lint it with a narrowly tailored ruleset. The worst option is to let generated files trigger noisy failures that developers are not meant to edit.
6. Rule severity matches team usage
Warnings that nobody reads are clutter. Errors on stylistic preferences can block useful work. Keep the config opinionated enough to catch mistakes, but not so strict that the team starts bypassing lint checks.
7. Your config treats tests as tests
Tests often need patterns that are discouraged in application code: looser typing in mocks, dev dependencies, setup globals, or focused assertions. Put tests in their own override block instead of weakening production rules globally.
8. The lint command is stable in local and CI runs
A reliable command matters more than a clever command. Use a predictable root path, a clear file target, and scripts that do the same thing locally and in CI. If developers run one command and CI runs another, config drift is more likely.
Common mistakes
These are the problems that repeatedly make TypeScript linting feel harder than it should.
Turning on type-aware rules too early
Type-aware linting is useful, but it is not always the right starting point. If your project is still settling its tsconfig, module boundaries, or path aliases, begin with syntax-level and recommended TypeScript rules first. Add type-aware rules after the project shape is stable.
Using one config block for every file in the repository
Flat config rewards explicit scoping. If app code, tests, config scripts, and generated files all share one ruleset, the result is usually friction. Separate blocks are not complexity for its own sake; they are documentation of how your repo actually works.
Linting build output
This wastes time and creates duplicate noise. Compiled output, generated declaration files, and framework artifacts should usually be ignored.
Trying to solve formatting, code quality, and architecture in one pass
ESLint can do many things, but a compact config is easier to trust. Start with correctness and maintainability. Add more specialized rules only when they fix a recurring problem in your team.
Keeping migration exceptions forever
Temporary overrides have a habit of becoming permanent. If you loosen rules for a JavaScript to TypeScript migration or for a single package, leave a comment that explains why and when it should be removed.
Assuming every recommended rule set fits every project
Recommended configurations are a starting point, not a contract. A UI-heavy app, a backend service, and a published library have different tolerance for rule strictness, naming patterns, and test conventions.
Ignoring TypeScript compiler feedback while tuning ESLint
ESLint and the TypeScript compiler overlap, but they are not interchangeable. If a lint error feels strange, ask whether it is really a compiler configuration issue first. That is often where the fix belongs.
When to revisit
A flat config is not something you set once and forget forever. Revisit it when the inputs change, especially before planning cycles or after toolchain updates. The most useful maintenance habit is a short review checklist.
Revisit your ESLint + TypeScript setup when:
- You adopt a new framework or build tool.
- You switch module systems or runtime targets.
- You add a package to a monorepo.
- You enable stricter TypeScript compiler options.
- You start generating client code, route types, or API types.
- You introduce a separate test runner or end-to-end test layer.
- Your lint run becomes noticeably slower.
- Your team starts ignoring warnings or disabling rules often.
Use this refresh checklist:
- Review ignored directories and remove stale entries.
- Confirm file globs still match the current source layout.
- Check whether type-aware rules should be expanded, reduced, or split by directory.
- Audit overrides for tests, scripts, and config files.
- Remove temporary migration exceptions that no longer apply.
- Compare lint failures from the last few weeks: are they useful, or mostly noise?
- Make sure the lint command is still identical in local development and CI.
If you want a practical rule for maintenance, use this one: every time you change project structure, revisit lint scope; every time you change TypeScript strictness, revisit lint rule depth.
That keeps your eslint typescript flat config aligned with the real codebase instead of the codebase you had six months ago. A good config should feel boring, clear, and dependable. If it does, developers will actually trust it.