/flags/nodejs/node-import-require-preload
node --import and -r (--require): preload a module before the app starts
- tool
- Node.js
- command
- node
- flag
- -r, --require MODULE; --import MODULE
- default
- none (nothing is preloaded)
node -r ./setup.cjs app.js (long form --require) loads a CommonJS module before the entry point runs; node --import ./setup.mjs app.js does the same for an ES module. Preloading is how tools such as APM agents, OpenTelemetry, dotenv/config, source map support and TypeScript loaders hook into an application without changing its code. Both flags can be repeated, and they also work inside NODE_OPTIONS.
Preload order
cat > setup.cjs <<'EOF'
console.log("setup.cjs (require)");
globalThis.startedAt = "preload";
EOF
cat > setup.mjs <<'EOF'
console.log("setup.mjs (import)");
EOF
cat > app.js <<'EOF'
console.log("app.js, startedAt =", globalThis.startedAt);
EOF
echo "--- no preload"
node app.js
echo "--- -r and --import"
node -r ./setup.cjs --import ./setup.mjs app.js
echo "--- through NODE_OPTIONS"
NODE_OPTIONS="--require ./setup.cjs" node app.js
echo "--- missing preload file"
node -r ./missing.cjs app.js 2>&1 | grep -m1 "Error:"Output
--- no preload app.js, startedAt = undefined --- -r and --import setup.cjs (require) setup.mjs (import) app.js, startedAt = preload --- through NODE_OPTIONS setup.cjs (require) app.js, startedAt = preload --- missing preload file Error: Cannot find module './missing.cjs'
--require modules are loaded first, then --import modules, then the entry point. A preload that cannot be found stops the process before any application code runs.
Comparison
| Aspect | -r, --require | --import |
|---|---|---|
| Module system | CommonJS (and ES modules through require(esm) in Node.js 22.12+) | ES modules, also CommonJS files |
| Top-level await | No | Yes |
| Typical use | dotenv/config, ts-node/register, older APM agents | module.register() loader hooks, OpenTelemetry ESM, tsx |
| Allowed in NODE_OPTIONS | Yes | Yes |
| Path resolution | Like require() from the current directory | Like import from the current directory; relative paths need ./ |
| Available since | Early Node.js versions | Node.js 20.6.0 and 18.18.0 |
Common commands
node -r dotenv/config app.js # load .env through the dotenv package
node --import tsx app.ts # TypeScript through the tsx loader
node --import ./instrumentation.mjs app.js # OpenTelemetry or another ESM instrumentation
NODE_OPTIONS="--require ./tracing.cjs" npm startA bare name like dotenv/config is resolved from node_modules in the current directory. A relative path must start with ./, otherwise Node.js looks for a package of that name and fails with Cannot find module.
Pitfalls
- Flags after the script name belong to the script:
node app.js -r ./setup.cjsdoes not preload anything. - The legacy
--loader/--experimental-loaderflag is discouraged; register hooks from a file passed to--importwithmodule.register()instead. - A preload in
NODE_OPTIONSalso runs in child processes and tools started with node (test runners, build scripts), which may load an agent twice or slow every command down. - An ES module preload with
-ron older releases fails withERR_REQUIRE_ESM; use--importthere. The differences between the two module systems are covered in require vs import.
FAQ
Should I use --require or --import?
Use --import for ES modules and for loader hooks registered with module.register(); use -r for CommonJS setup files. Both can be combined, and preloads run in the order they appear on the command line.
How do I preload a module for every node process?
Put the flag in NODE_OPTIONS, for example NODE_OPTIONS='--require ./tracing.cjs'. Every node process that inherits the variable, including child processes and npm scripts, loads the file.
Why does my preload run but my app does not see its variables?
Preloads share the process, not the module scope. Set values on globalThis or process.env, or export them and import the module again from the app; local variables in the preload are not visible.