error TS2451: Cannot redeclare block-scoped variable 'API_URL'. is reported when the same let, const or class name is declared twice in one scope. Inside a single file that is a plain mistake. Reported on two different files, it means the checker treats both files as scripts: files without import or export, whose top-level names are globals shared by every script.
Whether that is a bug depends on how the files run. Classic <script> tags without type="module" really do share one scope, and the second const API_URL throws a SyntaxError in the browser. Files that a bundler combines, or that load as modules, each have their own scope, and the checker just needs to be told.
At a glance
| Code | TS2451 |
|---|---|
| Message | Cannot redeclare block-scoped variable '{0}'. |
| TypeScript's category | error |
| Listed under | Unknown names and members |
| Depends on | moduleDetection, module |
| In .js files | Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file |
The message is TypeScript's own text from its diagnostic messages file (Apache-2.0); {0} is filled in by tsc for each error.
What it looks like
const API_URL = "/api/cart";error TS2451: Cannot redeclare block-scoped variable 'API_URL'.
let total = 0;error TS2451: Cannot redeclare block-scoped variable 'total'.jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"types": []
},
"include": ["*.js"]
} const API_URL = "/api/checkout";error TS2451: Cannot redeclare block-scoped variable 'API_URL'.
let total = 0;error TS2451: Cannot redeclare block-scoped variable 'total'.jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"types": []
},
"include": ["*.js"]
} What causes it
- Two or more
.jsfiles withoutimportorexportdeclare the same top-level name, and the config's module settings make them scripts. That happens withmoduleset topreserve,esnextor not set, under the defaultmoduleDetection: "auto". - With
module: "nodenext"(ornode16tonode20) every.jsfile is a module, so this error does not appear across files.
How to fix it
If the files are modules, set moduleDetection to force
Anything a bundler or a modern runtime loads is a module. "moduleDetection": "force" in jsconfig.json tells the checker so for every file, and the same two files pass unchanged.
const API_URL = "/api/cart";
let total = 0;jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"types": [],
"moduleDetection": "force"
},
"include": ["*.js"]
} Or mark each file with an empty export
export {}; at the end of a file makes it a module without exporting anything. Only do this for files that are loaded as modules; adding export to a classic script makes the browser reject it.
const API_URL = "/api/cart";
let total = 0;
export {};jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"types": []
},
"include": ["*.js"]
} For real classic scripts, keep the names apart
If the files are plain <script> tags, the clash is real. Rename one of the two, or wrap each file's code in a block (or an immediately invoked function) so its const and let names stay local.
{
const API_URL = "/api/cart";
let total = 0;
console.log(API_URL, total);
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"types": []
},
"include": ["*.js"]
} Gotchas
varand function declarations in two scripts do not clash with each other the same way (JavaScript allows redeclaring them), so the checker merges them instead; mixingvarin one file withletin another is still TS2451.- The moduleDetection page shows the same two files under each setting.
Sources
- TSConfig Reference: moduleDetection · TypeScript
- Modules: Theory (scripts and modules) · TypeScript handbook
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.