Skip to main content
Trace propagation enables distributed tracing by passing trace context between services, processes, and systems.

Overview

Propagation involves:
  • Extracting trace context from incoming requests
  • Injecting trace context into outgoing requests
  • Maintaining parent-child relationships across services
  • Preserving sampling decisions

Trace Headers

sentry-trace

The primary header for Sentry tracing: Format: {trace_id}-{span_id}-{sampled}
sentry-trace: 1234567890abcdef1234567890abcdef-1234567890abcdef-1
  • trace_id: 32 hex characters (128 bits)
  • span_id: 16 hex characters (64 bits)
  • sampled: 1 (sampled) or 0 (not sampled)

baggage

Contains dynamic sampling context: Format: Key-value pairs separated by commas
baggage: sentry-trace_id=1234...,sentry-public_key=abc...,sentry-release=1.0.0

traceparent (W3C)

Optional W3C Trace Context standard: Format: {version}-{trace_id}-{parent_id}-{flags}
traceparent: 00-1234567890abcdef1234567890abcdef-1234567890abcdef-01

Automatic Propagation

Sentry automatically propagates traces for HTTP requests:
import * as Sentry from '@sentry/node';
import { httpIntegration } from '@sentry/node';

Sentry.init({
  dsn: 'your-dsn',
  integrations: [
    httpIntegration({
      tracing: true // Enables automatic propagation
    })
  ],
  // Control which requests get tracing headers
  tracePropagationTargets: [
    'localhost',
    /^https:\/\/api\.myapp\.com/
  ]
});

// Headers automatically added to outgoing requests
await fetch('https://api.myapp.com/users');
// Includes: sentry-trace, baggage headers

tracePropagationTargets

Control which outgoing requests receive tracing headers:
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: 'your-dsn',
  tracePropagationTargets: [
    // String matching (partial match)
    'api.myapp.com',
    'internal-service',
    
    // RegExp matching
    /^https:\/\/api\./,
    /\.myapp\.com$/,
    
    // Relative URLs (browser only)
    /^\/api\//
  ]
});
Default Behavior:
  • Node.js: All outgoing requests
  • Browser: Same-origin requests only

Manual Propagation

Outgoing Requests

import * as Sentry from '@sentry/node';
import { spanToTraceHeader, spanToBaggageHeader } from '@sentry/core';

const span = Sentry.getActiveSpan();

if (span) {
  const headers = {
    'sentry-trace': spanToTraceHeader(span),
    'baggage': spanToBaggageHeader(span)
  };
  
  await fetch('https://api.example.com/data', { headers });
}

Incoming Requests

import * as Sentry from '@sentry/node';
import type { Request, Response, NextFunction } from 'express';

app.use((req: Request, res: Response, next: NextFunction) => {
  Sentry.continueTrace(
    {
      sentryTrace: req.headers['sentry-trace'] as string,
      baggage: req.headers['baggage'] as string
    },
    () => {
      // Request handling continues the trace
      next();
    }
  );
});

continueTrace

Continue a trace from incoming headers:
export function continueTrace<V>(
  {
    sentryTrace,
    baggage,
  }: {
    sentryTrace?: string;
    baggage?: string | string[];
  },
  callback: () => V,
): V
sentryTrace
string
The sentry-trace header value.
baggage
string | string[]
The baggage header value(s).
callback
function
required
Function to execute with the continued trace.
Example:
import * as Sentry from '@sentry/node';

const result = Sentry.continueTrace(
  {
    sentryTrace: request.headers['sentry-trace'],
    baggage: request.headers['baggage']
  },
  () => {
    // Process request with continued trace
    return Sentry.startSpan(
      { name: 'Process Request', op: 'http.server' },
      () => processRequest()
    );
  }
);

Cross-Service Tracing

Service A (Origin)

import * as Sentry from '@sentry/node';
import { spanToTraceHeader, spanToBaggageHeader } from '@sentry/core';

await Sentry.startSpan(
  { name: 'Call Service B', op: 'http.client' },
  async (span) => {
    const response = await fetch('https://service-b.example.com/api', {
      headers: {
        'sentry-trace': spanToTraceHeader(span),
        'baggage': spanToBaggageHeader(span)
      }
    });
    return response.json();
  }
);

Service B (Receiver)

import * as Sentry from '@sentry/node';

app.post('/api', (req, res) => {
  Sentry.continueTrace(
    {
      sentryTrace: req.headers['sentry-trace'],
      baggage: req.headers['baggage']
    },
    () => {
      Sentry.startSpan(
        { name: 'Handle Request', op: 'http.server' },
        async () => {
          const result = await handleRequest(req);
          res.json(result);
        }
      );
    }
  );
});

W3C Trace Context

Enable W3C traceparent header:
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: 'your-dsn',
  propagateTraceparent: true,
  tracePropagationTargets: ['example.com']
});

// Outgoing requests now include:
// - sentry-trace
// - baggage  
// - traceparent (W3C)
CORS Considerations: When enabling traceparent, ensure your server allows it:
// Server CORS config
res.setHeader(
  'Access-Control-Allow-Headers',
  'sentry-trace, baggage, traceparent'
);

Browser Propagation

Server-Side Rendering

Pass trace context from server to browser:
// Server (e.g., Next.js, Remix)
import * as Sentry from '@sentry/node';
import { spanToTraceHeader, getDynamicSamplingContextFromSpan } from '@sentry/core';

const span = Sentry.getActiveSpan();

if (span) {
  const sentryTrace = spanToTraceHeader(span);
  const dsc = getDynamicSamplingContextFromSpan(span);
  const baggage = dynamicSamplingContextToSentryBaggageHeader(dsc);
  
  // Inject into HTML
  const html = `
    <html>
      <head>
        <meta name="sentry-trace" content="${sentryTrace}" />
        <meta name="baggage" content="${baggage}" />
      </head>
      <body>...</body>
    </html>
  `;
}

Client-Side

// Browser
import * as Sentry from '@sentry/browser';

Sentry.init({
  dsn: 'your-dsn'
});

// Continue trace from meta tags
const sentryTrace = document
  .querySelector('meta[name="sentry-trace"]')
  ?.getAttribute('content');

const baggage = document
  .querySelector('meta[name="baggage"]')
  ?.getAttribute('content');

if (sentryTrace) {
  Sentry.continueTrace({ sentryTrace, baggage }, () => {
    // Browser continues server trace
  });
}

Message Queues

Propagate traces through async message queues:

Producer

import * as Sentry from '@sentry/node';
import { spanToTraceHeader, spanToBaggageHeader } from '@sentry/core';

const span = Sentry.getActiveSpan();

if (span) {
  const message = {
    data: payload,
    _sentry: {
      trace: spanToTraceHeader(span),
      baggage: spanToBaggageHeader(span)
    }
  };
  
  await queue.publish(message);
}

Consumer

import * as Sentry from '@sentry/node';

queue.subscribe(async (message) => {
  Sentry.continueTrace(
    {
      sentryTrace: message._sentry.trace,
      baggage: message._sentry.baggage
    },
    () => {
      Sentry.startSpan(
        { name: 'Process Message', op: 'queue.process' },
        () => processMessage(message)
      );
    }
  );
});

Database Connections

Propagate context through database operations:
import * as Sentry from '@sentry/node';
import { spanToTraceHeader } from '@sentry/core';

const span = Sentry.getActiveSpan();

if (span) {
  // Add trace context as SQL comment
  const traceComment = `/* sentry:${spanToTraceHeader(span)} */`;
  const query = `${traceComment} SELECT * FROM users`;
  
  await db.query(query);
}

Organization-Based Trace Continuation

Limit trace continuation to same organization:
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: 'your-dsn',
  
  // Only continue traces from same org
  traceContinuationOnlyIfSameOrg: true,
  
  // Optional: explicitly set org ID
  orgId: 'your-org-id'
});

// Traces from other orgs start new trace

Custom Propagation

Implement custom propagation logic:
import * as Sentry from '@sentry/node';
import type { Span } from '@sentry/types';

function propagateTraceToCustomProtocol(span: Span): Record<string, string> {
  const context = span.spanContext();
  
  return {
    'X-Trace-ID': context.traceId,
    'X-Span-ID': context.spanId,
    'X-Sampled': context.traceFlags === 1 ? 'true' : 'false'
  };
}

// Use with custom protocol
const span = Sentry.getActiveSpan();
if (span) {
  const headers = propagateTraceToCustomProtocol(span);
  await customProtocol.send({ headers, data });
}

Best Practices

1. Always Propagate Sampled Traces

const span = Sentry.getActiveSpan();
if (span && span.isRecording()) {
  // Only add headers if span is sampled
  headers['sentry-trace'] = spanToTraceHeader(span);
}

2. Handle Missing Headers Gracefully

app.use((req, res, next) => {
  const sentryTrace = req.headers['sentry-trace'];
  
  if (sentryTrace) {
    Sentry.continueTrace({ sentryTrace }, () => next());
  } else {
    // Start new trace
    next();
  }
});

3. Be Careful with CORS

// Only propagate to controlled origins
Sentry.init({
  tracePropagationTargets: [
    /^https:\/\/api\.myapp\.com/,
    // Don't add third-party APIs
  ]
});

4. Validate Incoming Headers

function isValidSentryTrace(trace: string): boolean {
  // Format: traceId-spanId-sampled
  const pattern = /^[a-f0-9]{32}-[a-f0-9]{16}-[01]$/;
  return pattern.test(trace);
}

if (sentryTrace && isValidSentryTrace(sentryTrace)) {
  Sentry.continueTrace({ sentryTrace }, callback);
}

Build docs developers (and LLMs) love