jsconfig.json option

moduleDetection

  • Default auto
  • Since TypeScript 4.7

A JavaScript file is either a module, with its own top-level scope, or a script, whose top-level names are globals shared with every other script. moduleDetection decides which files TypeScript treats as modules. Under "auto", the default, a file is a module when it has import or export, or when its module format says so; "force" makes every file a module; "legacy" looks only for import and export.

It matters when files have no imports or exports. With module: "nodenext", Node's rules already make every .js file a module, ES or CommonJS. With bundler or preserve, two such files that each declare const label are scripts in one global scope, and TypeScript reports TS2451 in both. The option arrived with TypeScript 4.7.

At a glance

Write Leave it at "auto" with nodenext. With bundler or preserve, write "force"
On the command line --moduleDetection
Type string
Default in TypeScript 5.9 auto
TypeScript's description Control what method is used to detect module-format JS files.
In the quick-start setups

The description is quoted from TypeScript's own option list (Apache-2.0); see the notices. The setup values are the checked configs on the quick-start pages.

Examples

a.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: a.js(2,7): error TS2451: Cannot redeclare block-scoped variable 'label'. b.js(2,7): error TS2451: Cannot redeclare block-scoped variable 'label'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. a.js(2,7): error TS2451: Cannot redeclare block-scoped variable 'label'. b.js(2,7): error TS2451: Cannot redeclare block-scoped variable 'label'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. a.js(2,7): error TS2451: Cannot redeclare block-scoped variable 'label'. b.js(2,7): error TS2451: Cannot redeclare block-scoped variable 'label'. checked 21 October 2026
const label = "a";error TS2451: Cannot redeclare block-scoped variable 'label'.
console.log(label);
moduleDetection auto with bundler: two scripts share one global scope.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "preserve",
    "types": [],
    "moduleResolution": "bundler"
  },
  "include": [
    "*.js"
  ]
}
a.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
const label = "a";
console.log(label);
moduleDetection force: every file is a module with a scope of its own.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "preserve",
    "types": [],
    "moduleResolution": "bundler",
    "moduleDetection": "force"
  },
  "include": [
    "*.js"
  ]
}

When files really are scripts

Classic browser <script> tags without type="module" do share one global scope, so there TS2451 is a real collision and "auto" is right. Use "force" when every file is in fact a module: anything a bundler or a modern runtime loads.

Gotchas

  • tsc --init in TypeScript 5.9 writes "moduleDetection": "force", and Bun's recommended config sets it too.

Sources

Last reviewed 20 October 2026.