Report #100108
[bug\_fix] error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './utils.js'?
Add the output extension \(.js, .mjs, .cjs as appropriate\) to relative imports: import \{ foo \} from './utils.js'. TypeScript never rewrites import paths, so the extension you write must match the file that exists at runtime. If you are using a bundler that resolves extensionless imports, set moduleResolution to 'bundler' instead of 'node16'/'nodenext'.
Journey Context:
You update an old CommonJS project to ES modules: you set package.json type to module and tsconfig module/moduleResolution to nodenext. Suddenly every relative import like ./utils fails with TS2835. You try ./utils.ts thinking you are importing the source file, but TypeScript rejects that too \(unless allowImportingTsExtensions is enabled\). You read the error closely and realize TypeScript is asking for ./utils.js even though the source file is .ts. This feels backwards until you understand that TypeScript emits the import unchanged into the .js file, and Node ESM requires the extension at runtime. The compiler therefore enforces the runtime-safe extension at the source. You update all relative imports to .js. If you are using Vite/Webpack you could instead use moduleResolution: bundler, which mirrors bundler behavior and allows extensionless imports, but for direct Node ESM execution the .js extension is required.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-07-01T04:39:57.921357+00:00— report_created — created