Skip to main content

Overview

ESM (ECMAScript Modules) applications use import statements rather than require(). Because dd-trace uses module load hooks to instrument third-party packages, ESM requires a different initialization approach from CommonJS.
You cannot call require('dd-trace').init() at the top of an ESM file and expect instrumentation to work. By the time your module executes, other ESM modules have already been evaluated. Use one of the approaches on this page instead.
Pass --import dd-trace/register.js on the command line. This registers dd-trace’s ESM loader hook before any application modules are loaded:
node --import dd-trace/register.js server.mjs
The register.js file calls node:module’s register() API to install loader-hook.mjs as a module customization hook:
register.js (dd-trace source)
const { register } = require('node:module')
const { pathToFileURL } = require('node:url')

register('./loader-hook.mjs', pathToFileURL(__filename))

Configuration with —import

Provide all configuration through environment variables when using --import dd-trace/register.js:
DD_SERVICE=my-service \
DD_ENV=production \
DD_VERSION=1.0.0 \
node --import dd-trace/register.js server.mjs

Programmatic configuration with —import

--import dd-trace/register.js reads configuration from environment variables only. For programmatic configuration alongside ESM, use the built-in dd-trace/initialize.mjs via --loader (which also self-registers the hook), or pass configuration through environment variables when using --import:
DD_SERVICE=my-service DD_ENV=production DD_LOGS_INJECTION=true \
  node --import dd-trace/register.js server.mjs
Alternatively, use the dd-trace/initialize.mjs module with --loader, which both registers the hook and initializes the tracer programmatically — but note --loader is deprecated as of Node.js 20:
node --loader dd-trace/initialize.mjs server.mjs
Do not pass a custom user-written ESM module to --import for tracer configuration. A module loaded via --import runs after the module system is set up, so simply calling tracer.init() inside it will not register the ESM instrumentation hooks for subsequently loaded modules. Always use --import dd-trace/register.js (and configure via environment variables) or --loader dd-trace/initialize.mjs.

package.json scripts

package.json
{
  "type": "module",
  "scripts": {
    "start": "node --import dd-trace/register.js server.mjs",
    "start:dev": "DD_ENV=development node --import dd-trace/register.js server.mjs"
  }
}

NODE_OPTIONS

NODE_OPTIONS='--import dd-trace/register.js' node server.mjs
Or set it in your shell profile / deployment environment so it applies to all Node.js processes.

Legacy: —loader flag

For Node.js versions before 18.19, use the --loader flag:
node --loader dd-trace/loader-hook.mjs server.mjs
The --loader API is deprecated as of Node.js 20.x and may emit an ExperimentalWarning. Use --import with Node.js >= 18.19 whenever possible.
Suppress the experimental warning in older Node.js:
NODE_NO_WARNINGS=1 node --loader dd-trace/loader-hook.mjs server.mjs

Mixed CJS/ESM projects

If your application mixes CommonJS and ESM (for example, a CommonJS server that imports ESM helpers), use --import dd-trace/register.js. The loader hook handles both module systems when registered via --import.

TypeScript ESM

For TypeScript projects compiled to ESM, use the same --import approach on the compiled output:
package.json
{
  "scripts": {
    "build": "tsc",
    "start": "node --import dd-trace/register.js dist/server.js"
  }
}
For ts-node with ESM:
node --import dd-trace/register.js --loader ts-node/esm src/server.ts

Verifying ESM instrumentation

Enable startup logs to confirm the loader hook was registered:
DD_TRACE_STARTUP_LOGS=true node --import dd-trace/register.js server.mjs
Enable debug logging to see which ESM modules are being intercepted:
DD_TRACE_DEBUG=true node --import dd-trace/register.js server.mjs

Known limitations

  • Some packages are excluded from ESM instrumentation by default: import-in-the-middle, langsmith, certain OpenAI internal shims, and Anthropic SDK shims.
  • Packages that use private class fields or other features incompatible with module wrapping may not be instrumentable via ESM hooks.
  • For bundled ESM output, see the Bundling guide.

Build docs developers (and LLMs) love