error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './format.js'? is reported for import ... from "./format" in an ES module under nodenext resolution. Node.js's ES module loader does not guess extensions or look for index.js, so the import would fail when the code runs.
TS2834 is the same rule when the checker has no single suggestion, typically a folder import such as "./utils", which Node.js would need written as "./utils/index.js".
At a glance
| Code | TS2835 |
|---|---|
| Message | Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '{0}'? |
Also on this page: TS2834 | Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Consider adding an extension to the import path. |
| TypeScript's category | error |
| Listed under | Modules and imports |
| Depends on | moduleResolution, module |
| In .js files | Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file |
The messages are 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
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'?
import { slugify } from "./utils";error TS2834: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Consider adding an extension to the import path.
export const label = `${slugify("Cotton tee")}: ${formatPrice(1999)}`;jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["**/*.js"]
} What causes it
- The file is an ES module (a
.jsfile in a package with"type": "module", or a.mjsfile) andmoduleResolutionisnode16ornodenext. - The import leaves out
.js, or points at a folder and relies onindex.js, habits from CommonJS and bundlers.
How to fix it
Write the path Node.js resolves
Add the extension, ./format.js, and spell out folder imports, ./utils/index.js. In a JavaScript project the path is simply the real file name.
import { formatPrice } from "./format.js";
import { slugify } from "./utils/index.js";
export const label = `${slugify("Cotton tee")}: ${formatPrice(1999)}`;jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["**/*.js"]
} Or, if a bundler resolves your imports, use bundler resolution
Code that only ever runs through Vite, webpack or another bundler can keep extensionless imports with "module": "preserve" and "moduleResolution": "bundler". Do not use this for code Node.js runs directly.
import { formatPrice } from "./format";
import { slugify } from "./utils";
export const label = `${slugify("Cotton tee")}: ${formatPrice(1999)}`;jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "preserve",
"types": [],
"moduleResolution": "bundler"
},
"include": ["**/*.js"]
} Gotchas
- CommonJS files under
nodenext(a package without"type": "module") may still import without extensions, becauserequireresolution adds them; only ES modules are held to the rule. - A path with the
.jsextension works under everymoduleResolutionmode,bundlerincluded, so writing extensions everywhere keeps code portable between the two.
Sources
- Modules Reference: node16, nodenext · TypeScript handbook
- Node.js: ECMAScript modules, mandatory file extensions · Node.js documentation
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.