Baggage vs span attributes

2 min read

Short answer

Baggage is a set of key-value pairs carried in the context and sent to downstream services, by default in a W3C baggage header. Span attributes are stored on one span and exported with it. Baggage doesn't become an attribute by itself: something has to read it and copy it onto the span.

On this page

You want every span in an agent run to say which agent it was. You set gen_ai.agent.id in baggage, and the spans still don’t show it. Meanwhile, the ID went out in the headers of every outgoing request.

OpenTelemetry baggage holding an agent ID is sent downstream in the baggage header automatically, but reaches a span attribute only when code copies it.

Where does each one go?

Baggage Span attribute
Lives in The context One span
Crosses process boundaries Yes, as a baggage header No
Exported to your backend Only if copied onto a span Yes
Typical use Pass a value to code you don’t control Describe the operation

The baggage docs say it plainly: baggage is a separate key-value store, unrelated to span attributes unless you add them explicitly.

Both, side by side

import { context, propagation } from '@opentelemetry/api';
const bag = propagation.createBaggage({ 'gen_ai.agent.id': { value: 'run-7f3a' } });
context.with(propagation.setBaggage(context.active(), bag), () => {
tracer.startSpan('send 8453').end(); // baggage is not copied to the span
const headers: Record<string, string> = {};
propagation.inject(context.active(), headers); // but it is sent downstream
console.log(headers.baggage);
const id = propagation.getBaggage(context.active())?.getEntry('gen_ai.agent.id')?.value;
tracer.startSpan('send 8453', { attributes: { 'gen_ai.agent.id': id } }).end(); // copied by hand
});
gen_ai.agent.id=run-7f3a
send 8453 {}
send 8453 { 'gen_ai.agent.id': 'run-7f3a' }

The first span has no attributes, yet the header carries the ID. The second span has it because the code copied it.

Who can set your baggage?

Anyone who calls you. A service that accepts requests from outside reads whatever baggage header arrives. If your instrumentation trusts it, a caller can label its work with an agent ID of their choosing. Your own baggage, in turn, goes to every API your instrumented HTTP client calls.

Rule of thumb: baggage for values that are fine to share and fine to be spoofed, attributes for everything else.

FAQ

Does OpenTelemetry add baggage to spans automatically?

No. Baggage is a separate key-value store. Your code, or a baggage span processor, has to read the entries and set them as attributes.

How is baggage propagated?

The W3C Baggage propagator writes it into a baggage header on outgoing requests, and the receiving service reads it back. NodeTracerProvider.register() installs that propagator by default.

Is it safe to put user data in baggage?

Only if every service it reaches, including third-party APIs your instrumented HTTP client calls, may see it. Baggage leaves your process in plain headers.

Go further

Type to search the docs, Learn topics and the blog.