Duplicate identifier errors in TypeScript usually mean the compiler is seeing the same name defined twice, but the real cause is often less obvious than the message suggests. The conflict may come from mixed browser and Node globals, overlapping test environment types, duplicated .d.ts files, multiple package versions, or a script file that accidentally leaks declarations into the global scope. This guide gives you a practical way to diagnose and fix these errors, with examples you can reuse when your stack changes.
Overview
If you searched for duplicate identifier TypeScript or TypeScript duplicate identifier fix, the first thing to know is that this is not one single bug. It is a class of name-collision problems. TypeScript builds a program from your source files, dependency types, library definitions, and compiler options. When two declarations with the same name land in the same scope and cannot legally merge, the compiler stops with an error.
Common messages include:
Duplicate identifier 'X'Cannot redeclare block-scoped variable 'X'- Conflicts involving globals like
Node,Request,URL,AbortController, or test functions such asdescribeandit - A
d.tsduplicate error coming from generated types or hand-written declaration files
The fastest way to debug these errors is to stop treating them as syntax problems and start treating them as scope problems. Ask three questions:
- What exact identifier is duplicated?
- Which two files or packages define it?
- Why are both definitions being included in the same compilation?
That framing usually gets you to the fix faster than changing random compiler options.
Here are the most common root causes:
- A file without imports or exports is treated as a script, so its declarations become global.
- Your
tsconfig.jsonincludes libraries you do not actually need, such as DOM types in a backend-only project. - Test framework types overlap, for example Jest and Vitest, or browser and Node test helpers in the same config.
- More than one version of a typed dependency is installed in a workspace or monorepo.
- You have custom declaration files that repeat names from built-in or package-provided types.
- Generated type output is accidentally compiled alongside source declarations.
Before changing anything, run the compiler in a way that exposes context. In practice, that means opening the exact error locations and checking your active tsconfig.json, your include/exclude patterns, and the types and lib options. If your issue is broader than duplicate identifiers, the companion guide on fixing TypeScript module resolution errors is often useful too, because many naming conflicts are really dependency-loading problems in disguise.
Maintenance cycle
This topic is worth revisiting on a regular cycle because duplicate identifier errors often appear after routine project maintenance. You update a test runner, add a framework plugin, enable a new runtime target, split a package in a monorepo, or generate new declarations. Suddenly the compiler sees names it did not see before.
A practical maintenance cycle looks like this:
1. Review compiler boundaries quarterly
Every few months, inspect the files your TypeScript project is compiling. Pay attention to:
includeandexcludeglobs- whether build output directories like
dist,coverage, or generated folders are excluded - whether test files and application files share the same config unnecessarily
- whether hand-written
.d.tsfiles still reflect current package usage
This is especially important in larger repos. A stale glob can silently pull in files that should not be part of the program.
2. Re-check lib and types whenever the runtime changes
If your project moves between browser, Node.js, serverless, edge, React Native, or hybrid environments, revisit the libraries and ambient types you include. Many global types sound universal but are defined differently across environments.
For example, a Node API project usually does not need the DOM library. If lib includes DOM by default or through a shared base config, you may introduce browser globals that conflict with runtime-specific types.
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"lib": ["ES2022"],
"types": ["node"]
}
}That kind of narrow configuration reduces accidental global overlap.
3. Audit workspace dependency versions after package upgrades
Monorepos are frequent sources of duplicate type problems because different packages can pull in different versions of the same dependency or its type package. Even when runtime code still works, TypeScript may load multiple declarations for the same symbol.
When you update dependencies, check whether a core package now exists twice in your lockfile or package tree. If you maintain a multi-package repository, the guide on TypeScript monorepo setup with project references and shared types is useful background for keeping package boundaries clean.
4. Separate environment-specific configs
Many duplicate identifier errors disappear when you stop compiling everything with one broad tsconfig.json. Instead, keep a base config and extend it for app code, test code, scripts, and tooling.
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"types": ["vitest/globals", "node"]
},
"include": ["src/**/*.test.ts", "src/**/*.spec.ts"]
}This keeps test globals out of your production build and reduces collisions.
Signals that require updates
You do not need to wait for a compiler failure before revisiting this setup. Certain changes are strong signals that duplicate identifier issues may be close behind.
You added or switched test tooling
Jest, Vitest, Cypress, Playwright, and other tools often inject globals. If two frameworks define the same helper names, your types can collide. One common mistake is leaving old test types in tsconfig.json after a migration.
If you moved from Jest to Vitest, for example, make sure you did not keep both:
{
"compilerOptions": {
"types": ["jest", "vitest/globals"]
}
}That may work for some files, but it is an easy way to invite global test conflicts. Prefer environment-specific configs instead.
You introduced custom declaration files
A custom global.d.ts or env.d.ts is often helpful, but it is also one of the easiest ways to create a d.ts duplicate error. This usually happens when you define something that already exists in a library or when you place global declarations in multiple files without realizing they combine into the same namespace.
Be careful with declarations like:
declare interface Request {
user?: { id: string };
}That name may already mean different things depending on whether your project includes DOM, Fetch, Express, or framework-specific types. In many cases, a more targeted module augmentation is safer than widening a global interface.
You changed build tools or output structure
Generated declarations, copied source files, and mixed ESM/CJS output can all create duplicate visibility. If your build now emits .d.ts files into a folder that TypeScript still compiles as input, you may be compiling both source and generated declarations together.
After changing bundlers or build pipelines, review your exclusions. If you are comparing tooling behavior, the article on TypeScript build tools helps clarify where type-checking responsibilities actually sit.
You started seeing browser and server globals together
Hybrid frameworks can blur environment boundaries. A Next.js app, for example, can involve server code, client code, edge code, and test code in one repository. If you share one loose config across all of them, globals can spill into places where they do not belong. In that case, narrowing the project graph matters more than adding more type packages.
If your stack includes App Router patterns and mixed server/client modules, the Next.js + TypeScript guide is a good follow-up read.
Common issues
This section is the practical troubleshooting core. Start with the error message, then match it to the most likely cause.
1. A file is accidentally global
If a TypeScript file has no top-level import or export, it may be treated as a script rather than a module. Its declarations become global, which can lead to duplicate identifiers or cannot redeclare block-scoped variable errors.
Example:
const name = "app";If another script file declares the same variable, TypeScript can report a global redeclaration. A simple fix is to make the file a module:
export {};
const name = "app";This does not export your variable for use elsewhere; it just tells TypeScript the file has module scope.
2. DOM and Node types are loaded together without a plan
This is one of the most common forms of global types conflict TypeScript developers run into. Full-stack projects often inherit broad defaults. Backend packages then unexpectedly compile with browser globals in scope.
Fixes to try:
- Set
libexplicitly instead of relying on defaults. - Set
typesexplicitly instead of loading every visible type package. - Split frontend and backend into separate configs where possible.
Example backend-focused config:
{
"compilerOptions": {
"lib": ["ES2022"],
"types": ["node"]
}
}Example frontend-focused config:
{
"compilerOptions": {
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}Avoid combining these unless the package truly needs both.
3. Multiple test frameworks define overlapping globals
If describe, it, expect, or similar helpers are implicated, check whether more than one test framework is active. This often happens after migration or when a repo contains app tests, e2e tests, and library tests under one config.
Fixes:
- Create a dedicated
tsconfig.test.jsonfor each test environment. - Restrict the
typesarray to only the framework used by that package. - Avoid a root config that enables every test framework globally.
4. Duplicate package versions are installed
If the error points into node_modules, especially two different paths for similarly named declarations, inspect the dependency tree. One package may depend on an older type package while another depends on a newer one.
Typical remedies include:
- aligning dependency versions across workspace packages
- removing unnecessary direct installs of
@types/*packages if the library already ships its own types - deduping the lockfile or consolidating peer dependency usage
This is not always a TypeScript problem; sometimes it is package management hygiene.
5. Custom .d.ts files duplicate existing declarations
Hand-written declarations should be as narrow as possible. Broad names such as Request, Window, Event, or generic utility interfaces are frequent collision points.
Prefer module augmentation when you want to extend a library type rather than redefining a global name. For example, in Express you would usually augment the appropriate module instead of declaring a global request shape from scratch.
6. Build output is being compiled as input
If your source emits declarations into dist and your root config includes **/*.ts or all project files recursively, the compiler may pick up generated output on the next run.
Check for missing exclusions:
{
"exclude": ["dist", "build", "coverage", "node_modules"]
}This is a simple fix, but it is easy to miss in long-lived projects.
7. The temptation to use skipLibCheck
skipLibCheck can reduce noise, and some teams use it intentionally for performance. But it should not be your first answer to duplicate identifier errors. It can hide a real configuration problem and make future upgrades harder to reason about.
If you use it, treat it as a temporary pressure-release valve while you identify the actual overlap. Do not assume it fixes the underlying conflict.
A short diagnostic checklist
- Read the exact identifier and both file paths in the error.
- Confirm whether the conflict is in your code, generated code, or dependencies.
- Check if the file is a module or an accidental global script.
- Inspect
lib,types,include, andexclude. - Look for overlapping test environments.
- Check for duplicate dependency versions.
- Review any custom
.d.tsfiles.
That sequence solves most cases without guesswork.
When to revisit
Return to this topic whenever your project boundary changes, not just when the compiler fails. Duplicate identifier problems are maintenance issues as much as coding issues. They tend to appear when a project evolves faster than its TypeScript configuration.
Revisit your setup when:
- you add a new runtime environment such as Node, browser, edge, or workers
- you migrate test frameworks or add end-to-end tooling
- you introduce generated types, API clients, or schema-based codegen
- you split a repository into packages or combine packages into a monorepo
- you notice more ambient declarations, global helpers, or custom
.d.tsfiles accumulating - you upgrade framework versions and see new globals appear
A useful habit is to keep a small “type boundaries” review in your maintenance cycle. It can be as simple as opening each active tsconfig and checking:
- What runtime is this config for?
- Which globals should exist here?
- Which type packages are intentionally included?
- Are build artifacts excluded?
- Are tests isolated from app code?
If you want a practical next step, start with these actions today:
- Open your root
tsconfig.jsonand makelibandtypesexplicit. - Exclude build output directories if you have not already.
- Split test and app configs if they currently share one broad setup.
- Search for custom
.d.tsfiles and review every global declaration. - In a workspace, check whether duplicate dependency versions are present.
That is usually enough to prevent the same class of error from returning.
And if the issue you are seeing turns out not to be a duplicate at all, but a bad import path or mismatched alias, follow up with the guide to TypeScript path aliases. Many compiler errors travel together, and keeping your project boundaries clear is the best long-term fix for all of them.