Node looks at the file extension first, then at package.json:
.mjsfiles are always ESM (ES modules)..cjsfiles are always CommonJS.- A
.jsfile follows the nearestpackage.json."type": "module"makes it ESM."type": "commonjs", or notypefield, makes it CommonJS.
Recent Node versions can also detect ESM syntax in a .js file when the type is not clear. Still, setting type explicitly is clearer.
How they work together:
- ESM can
importCommonJS.module.exportsbecomes the default export. Node also tries to detect named exports, but this is best-effort and does not always work. - CommonJS can use
import()to load ESM, in every Node version. - Since Node 22.12 and 20.19,
require()can also load an ES module. This works only if the module and its imports have no top-levelawait. Otherwise it throwsERR_REQUIRE_ASYNC_MODULE.
In ESM, relative imports need file extensions. Also, require, __dirname and __filename are not defined.
.mjsis ESM,.cjsis CommonJS, and.jsfollows thetypefield inpackage.json.- ESM can import CommonJS, and
module.exportsbecomes the default export. - CommonJS can use
import(). In recent Node,require()can load ESM that has no top-level await. - ESM needs explicit file extensions and has no
requireor__dirname.
Read more