.agents/skills/apm-integrations/references/new-integration-guide.md
Step-by-step checklist for creating a new dd-trace-js integration from scratch.
Orchestrion requires three files:
1. JSON config — packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/<name>.js:
module.exports = [{
module: {
name: '<npm-package>',
versionRange: '>=1.0.0',
filePath: 'dist/client.js' // file containing the target method
},
functionQuery: {
methodName: 'query',
className: 'Client',
kind: 'Async' // Async | Callback | Sync
},
channelName: 'Client_query'
}]
To find filePath, inspect the installed package to locate where the target method is defined. Many libraries duplicate classes across separate CJS and ESM builds (e.g., dist/cjs/client.js and dist/esm/client.js). Add a separate entry for each file path with the same functionQuery and channelName — otherwise the uninstrumented module format will silently fail.
2. Hooks file — packages/datadog-instrumentations/src/<name>.js:
'use strict'
const { addHook, getHooks } = require('./helpers/instrument')
for (const hook of getHooks('<npm-package>')) {
addHook(hook, exports => exports)
}
getHooks reads the orchestrion config and generates addHook entries automatically. This file is needed so the module hooks are registered for the rewriter to process.
3. hooks.js entry — (see Register in hooks.js below)
See Orchestrion Reference for the full config schema, ESQuery support, and channel naming.
Create packages/datadog-instrumentations/src/<name>.js. Always add a comment explaining why orchestrion is not viable!!!
When using shimmer, prefer tracingChannel over manual channels. tracingChannel (from dc-polyfill or diagnostics_channel) automatically provides start, end, asyncStart, asyncEnd, and error events — less boilerplate and consistent with how orchestrion works internally.
Streaming example (the main case where shimmer is needed — intercepting emitted events on returned stream objects):
'use strict'
// Shimmer required: <library> returns a stream object whose events must be
// intercepted — orchestrion wraps method return values, not emitted events.
const { addHook } = require('./helpers/instrument')
const { tracingChannel } = require('dc-polyfill')
const shimmer = require('../../../datadog-shimmer')
// tracingChannel is preferred over manual channels — provides start/end/asyncStart/asyncEnd/error automatically
const ch = tracingChannel('apm:<name>:<operation>')
addHook({ name: '<module-name>', versions: ['>=1.0'] }, (moduleExports) => {
shimmer.wrap(moduleExports.prototype, 'query', function (original) {
return function wrappedQuery (...args) {
if (!ch.start.hasSubscribers) {
return original.apply(this, args)
}
const ctx = { query: args[0], client: this }
return ch.start.runStores(ctx, () => {
try {
const stream = original.apply(this, args)
// Wrap emit to intercept stream lifecycle events
shimmer.wrap(stream, 'emit', function (emit) {
return function (event, arg) {
switch (event) {
case 'error':
ctx.error = arg
ch.error.publish(ctx)
break
case 'end':
ch.asyncEnd.publish(ctx)
break
}
return emit.apply(this, arguments)
}
})
return stream
} finally {
ch.end.publish(ctx)
}
})
}
})
return moduleExports
})
For other shimmer patterns, refer to existing shimmer-based instrumentations in the codebase (e.g., packages/datadog-instrumentations/src/pg.js). For tracingChannel usage, see packages/datadog-instrumentations/src/undici.js or packages/datadog-instrumentations/src/aerospike.js.
Both orchestrion and shimmer paths require an entry in packages/datadog-instrumentations/src/helpers/hooks.js:
module.exports = {
// Orchestrion or CJS-only shimmer:
'<name>': () => require('../<name>'),
// Shimmer with ESM/dual packages (orchestrion handles ESM automatically):
'<name>': { esmFirst: true, fn: () => require('../<name>') },
}
mkdir -p packages/datadog-plugin-<name>/{src,test}
| If the library... | Use | Import | Key Features |
|---|---|---|---|
| Queries a database | DatabasePlugin | ../../dd-trace/src/plugins/database | DBM comment injection, db.* tags |
| Caches data (Redis, Memcached) | CachePlugin | ../../dd-trace/src/plugins/cache | Cache-specific tags |
| Makes HTTP/RPC requests | ClientPlugin | ../../dd-trace/src/plugins/client | Peer service, distributed tracing headers |
| Handles HTTP requests | ServerPlugin | ../../dd-trace/src/plugins/server | Request/response lifecycle |
| Routes requests (middleware) | RouterPlugin | ../../dd-trace/src/plugins/router | Middleware span tracking, route extraction |
| Produces messages | ProducerPlugin | ../../dd-trace/src/plugins/producer | DSM integration, messaging tags |
| Consumes messages | ConsumerPlugin | ../../dd-trace/src/plugins/consumer | DSM integration, messaging tags |
| Has multiple operations | CompositePlugin | ../../dd-trace/src/plugins/composite | Combines sub-plugins |
| Injects trace context into logs | LogPlugin | ../../dd-trace/src/plugins/log | No spans, log correlation |
| None of the above | TracingPlugin | ../../dd-trace/src/plugins/tracing | Generic span creation |
Wrong base class = complex workarounds. If fighting the base class, the choice is probably wrong.
Create packages/datadog-plugin-<name>/src/index.js (adapt base class and tags to library type):
'use strict'
const DatabasePlugin = require('../../dd-trace/src/plugins/database')
class MyPlugin extends DatabasePlugin {
static id = '<name>'
static operation = '<operation>'
// Channel prefix determines how the plugin subscribes to instrumentation events.
// Three patterns exist — set `static prefix` explicitly based on instrumentation type:
//
// Orchestrion: static prefix = 'tracing:orchestrion:<npm-package>:<channelName>'
// Shimmer + tracingChannel: static prefix = 'tracing:apm:<name>:<operation>'
// Shimmer + manual channels: omit prefix — defaults to `apm:${id}:${operation}`
static peerServicePrecursors = ['db.name']
bindStart (ctx) {
// Orchestrion: use ctx.arguments and ctx.self
// Shimmer: use named properties like ctx.sql, ctx.client
const { sql, client } = ctx
this.startSpan(this.operationName(), {
service: this.serviceName({ pluginConfig: this.config }),
resource: sql,
type: 'sql',
meta: {
component: '<name>',
'db.type': '<name>'
}
}, ctx)
return ctx.currentStore
}
}
module.exports = MyPlugin
For CompositePlugin (multiple operations like produce + consume), create separate sub-plugin files in src/. See Plugin Patterns for the composite pattern and detailed base class examples.
Add to packages/dd-trace/src/plugins/index.js:
get '<module-name>' () { return require('../../../datadog-plugin-<name>/src') },
If multiple npm packages map to the same plugin (e.g., redis and @redis/client), add one getter per name.
In index.d.ts, add to the plugins namespace:
// In the Plugins interface:
'<name>': plugins.<name>;
// Add plugin interface (alphabetical order):
interface <name> extends Instrumentation {}
// With config options:
interface <name> extends Instrumentation {
optionName?: string | boolean;
}
Add type-check call:
tracer.use('<name>');
tracer.use('<name>', { optionName: 'value' });
Add section alphabetically:
<h5 id="<name>"><h5>
This plugin automatically patches the [<LibraryName>](<url>) module.
| Option | Default | Description |
|--------|---------|-------------|
| `service` | | Service name override. |
Add to .github/workflows/apm-integrations.yml:
<name>:
runs-on: ubuntu-latest
env:
PLUGINS: <name>
# SERVICES: <docker-service> # if external services needed, plus the service configuration. should match docker-compose
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node
with:
version: ${{ matrix.node-version }}
- uses: ./.github/actions/install
- run: yarn test:plugins:ci
strategy:
matrix:
node-version: [18, 22]
Check the existing workflow for the current step format.
See Testing for complete templates.
Unit tests — packages/datadog-plugin-<name>/test/index.spec.js
ESM integration tests — packages/datadog-plugin-<name>/test/integration-test/
# CI command (preferred) — handles dependency installation via yarn services
PLUGINS="<name>" npm run test:plugins:ci
packages/dd-trace/src/plugins/index.jsindex.d.tsdocs/test.tsdocs/API.md.github/workflows/apm-integrations.yml