moduleResolution is the lookup algorithm: given "./format" or "some-package/sub", which file does the import mean? nodenext applies Node's rules (extensions required in ES modules, exports maps honored); bundler applies the looser rules bundlers share (extensions optional, exports read with the import condition).
module: "nodenext" implies nodenext and module: "preserve" implies bundler, so most configs never write it. bundler arrived with TypeScript 5.0. The old node10 (also spelled node) ignores exports maps and is deprecated from TypeScript 6.0; classic is removed there.
At a glance
| Write | Usually nothing: let module imply it. Write "bundler" next to module: "esnext" |
|---|---|
| On the command line | --moduleResolution |
| Type | string |
| Default in TypeScript 5.9 | node10 (with module: "commonjs"; module nodenext implies nodenext, and preserve implies bundler) |
| TypeScript's description | Specify how TypeScript looks up a file from a given module specifier. |
| 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
import { formatPrice } from "./format";error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './format.js'?
console.log(formatPrice(1999));jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} import { formatPrice } from "./format";
console.log(formatPrice(1999));jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "preserve",
"types": [],
"moduleResolution": "bundler"
},
"include": [
"*.js"
]
} Which one matches your project
Choose by who resolves the imports when the code runs. If Node.js loads your files directly, use nodenext, so the checker refuses imports that Node.js would refuse. If a bundler, Bun, or a framework's build resolves them, use bundler, so the checker accepts what that tool accepts. A browser that loads <script type="module"> files without a bundler needs exact relative URLs, which nodenext with "type": "module" enforces; TypeScript's handbook suggests that pairing for unbundled browser code.
Gotchas
- With
bundler, the checker acceptsimport "./format", but Node.js would not. Use it only for code that something other than Node.js resolves. - An import the checker cannot resolve is an error only when it binds something;
import "./polyfill.js"with a wrong path passes unlessnoUncheckedSideEffectImportsis on.
Sources
- TSConfig Reference: moduleResolution · TypeScript
- Modules: Reference · TypeScript handbook
- Modules: Choosing Compiler Options · TypeScript handbook
- TypeScript 5.0 release notes · TypeScript handbook
- TypeScript 6.0 release notes · TypeScript handbook
Last reviewed 20 October 2026.