What Is Baggage in OpenTelemetry?
A plain-English guide to the small set of labels that travels with a request from service to service, so that every step along the way knows who the request is for and why it exists, and to the dangers that come with carrying it.
Imagine you are travelling through several airports on one long trip. At each stop, the staff look at your passport and your ticket, but they also look at the tags on your suitcase. One tag says “priority”, another says “fragile”, and a third says “final stop: Delhi”. Nobody has to phone the first airport to ask what to do with your bag. The instructions simply travel with it.
Software requests need the same thing. A request enters through a front door, passes through ten different services, and each of them might want to know something that only the front door knew, such as “this customer is on the premium plan” or “this request comes from the mobile app”. Asking the front door again at every step would be slow and awkward. OpenTelemetry offers a neat answer: a small bag of labels that rides along with the request. That bag is called baggage.
In this guide, you will learn what baggage is, how it differs from the other things that travel with a request, how it moves across the network, how the SDK handles it, and, just as importantly, the risks that come with it. No prior knowledge is needed. Every new word is explained the first time it appears.
1What Is Baggage?
Baggage is a small collection of name and value pairs that is attached to a request and carried across every service the request visits.
A set of labels that travels
Baggage in OpenTelemetry is a set of key and value pairs, such as “customer tier equals premium” or “region equals europe”. It is part of the request’s context, the invisible package that follows the request through your code. When one service calls another over the network, the baggage is written into the outgoing message, and the receiving service reads it back out. Every service in the chain can look at the baggage, add to it, or remove from it.
Not telemetry, but a helper for telemetry
This is the first thing to understand clearly: baggage is not itself telemetry. It does not show up in your traces, logs, or metrics on its own. It is a delivery service for small facts. What you do with the facts, such as copying them onto spans or using them to decide behavior, is up to you.
Think of a school trip. Each child wears a wristband with the group name and a note, such as “allergic to peanuts”. Every teacher, guide, and bus driver along the trip can read the wristband. The band is not a diary of the trip. It is information carried to every place the child goes. Baggage is the wristband of a request.
Remember the three verbs: set at one place, travel with the request, and read at another place. Everything about baggage fits into those three steps.
2Why Baggage Exists: The Problem It Solves
Every feature exists because of a pain. Let us look at the pain that baggage removes.
Facts known at the front, needed at the back
Many facts are known only at the beginning of a request. The front door knows which customer plan is being used, which mobile app version sent the request, and which experiment group the user is in. But the services deep in the chain, such as pricing, recommendations, or payment, may want exactly these facts. A payment service that wants to add a “premium customer” label to its telemetry cannot see the original login. It sees only a request from the order service.
The awkward alternatives
- Ask again. Each service looks up the customer record again. It works, but it adds extra calls, extra load, and extra delay for every request.
- Add a parameter to every call. Every function and every API between services must carry the extra field, even those that never use it. Changing hundreds of interfaces is slow and error-prone.
- Invent your own header. Every team invents a different header and a different format, and nothing works with anything else.
Baggage replaces all of these with one standard, agreed way. Put the facts in once, near the front. The SDK and the propagation rules carry them everywhere the request goes, with no change to function signatures and no extra lookups.
Imagine a hospital where the patient’s allergy was recorded at the entrance. Without a wristband, every ward has to call the reception to ask. Without a standard, each ward would invent its own coloured card. With a standard wristband, every nurse sees the allergy at once. Baggage is that standard wristband for software.
A short history
The idea is older than OpenTelemetry. The earlier OpenTracing project had “baggage items” that were attached to a span’s context and traveled with it. OpenCensus had a similar idea called tags. When these projects merged to form OpenTelemetry, baggage was redesigned as its own feature, separate from spans, so that it can be used even when no tracing is active.
3Core Vocabulary: The Words You Need
A small set of words appears in every discussion of baggage. Learn them once, and the rest of the guide will be easy.
| Term | Plain meaning | Example |
|---|---|---|
| Baggage | The whole set of key and value pairs carried with a request | Customer tier and region together |
| Entry (or member) | One key with its value | “region” with the value “europe” |
| Key (or name) | The label of an entry | “customer tier” |
| Value | The content of an entry, always text | “premium” |
| Metadata (or properties) | An optional extra note attached to an entry | A tag saying the entry is for internal use |
| Context | The invisible package that follows the code and holds baggage and the active span | The request’s backpack |
| Propagator | The component that writes context into outgoing messages and reads it from incoming ones | The W3C baggage propagator |
| Inject | Write the baggage into an outgoing request | Adding the header to an HTTP call |
| Extract | Read the baggage from an incoming request | Reading the header on arrival |
| Propagation | The whole act of carrying context between services | Inject, travel, extract |
The word “bag”
A good way to remember the word is to keep the picture of a bag with tags. The bag can be passed from hand to hand. Anybody holding it can look inside, put a new tag in, or take a tag out. And because it travels with the traveler, it reaches every stop.
Baggage values are text. If you need to carry a number or a yes-or-no answer, you write it as text, such as “42” or “true”, and the reader turns it back into the type it needs.
4Baggage vs Trace Context vs Attributes vs Resource
Beginners often mix up baggage with several neighbors. Each does a different job. Let us separate them carefully.
| Item | What it describes | Travels to other services? | Appears in telemetry automatically? |
|---|---|---|---|
| Trace context | Where the request sits in a trace (trace ID, span ID, sampling flag) | Yes | Yes, as IDs on spans and logs |
| Baggage | Free-form facts you chose to share about the request | Yes | No |
| Span attributes | Details about one single step | No | Yes, on that span |
| Resource attributes | Facts about the service producing telemetry | No | Yes, on all its telemetry |
| Trace state | Vendor-specific hints that ride with trace context | Yes | Sometimes, depending on the tool |
The easy way to tell them apart
Trace context
Answers “which request is this, and where in the story are we?” It is created by the system and must be kept intact.
Baggage
Answers “what do I want every downstream service to know about this request?” You choose the contents.
Span attributes
Answers “what happened in this one step?” They stay on that single span and go nowhere else.
Resource attributes
Answers “which service, version, and machine produced this?” They are fixed for the life of the process.
A parcel has a tracking number, which is the trace context, and it identifies the parcel and its journey. A sticker saying “fragile, handle with care” is the baggage, a message that every handler along the way should see. The note a worker writes at one warehouse, such as “box dented at loading dock”, is a span attribute. The name of the warehouse itself, printed on the stamp, is a resource attribute.
Putting a value into baggage does not put it on any span. If you set “customer tier” in baggage and then search your traces for it, you will find nothing, unless you also copy it to span attributes. Chapter 11 explains how.
5The Anatomy of a Baggage Entry
What exactly is inside the bag? Each entry is small and simple, and the rules about size and format keep it that way on purpose.
Key
The name of the entry, such as a lowercase, dotted name. Keys should be short, descriptive, and consistent across your company.
Value
The text content. Special characters are encoded when written to the wire, so they travel safely.
Metadata
An optional note attached to the entry. The SDK treats it as opaque text and passes it along without interpreting it.
Rules that keep the bag small and safe
- Text only. Both keys and values are text. Numbers and flags are written as text.
- Size limits. The standard sets minimum limits that every implementation must support, roughly on the order of dozens of entries and a few kilobytes in total. Going beyond the limits means that some entries may be dropped.
- Unchangeable once built. A baggage object cannot be edited in place. Setting a value creates a new baggage object with the change, and that new object is placed into the context.
- One value per key. If you set the same key again, the new value replaces the old one.
Think of the luggage tag on a suitcase. It is small, so only a few short words fit. You can change what the tag says only by replacing it with a new tag. Airlines also set a limit on how much tag you can attach. Baggage follows the same philosophy: small, simple, and replaceable.
Choose keys with a clear prefix, such as the name of your company or team, so that two teams do not accidentally use the same key for different meanings.
6How Baggage Travels: The Wire Format
When baggage crosses a network, it must be written into the message in a format that every service understands. That format is a public standard.
The baggage header
The standard for carrying baggage is the W3C Baggage format. For web requests, it uses an HTTP header named baggage. The value of the header is a comma-separated list of entries, written as key, an equals sign, and value. For example, a header could carry “customer.tier=premium” and “region=eu” together, separated by a comma. Special characters in values are percent-encoded so that they cannot break the format. An entry’s optional metadata is added after a semicolon.
flowchart LR
GW["API Gateway sets customer tier and region"] -- "baggage header" --> OS["Order Service reads and forwards"]
OS -- "baggage header" --> PS["Payment Service reads"]
OS -- "baggage header" --> IS["Inventory Service reads"]
Read the figure from left to right. The gateway puts the labels in at the start. The order service receives them, can read them, and passes them on to both the payment and inventory services. Nobody had to write any special code to look up the customer tier again.
Same idea for other protocols
HTTP is only one road. For gRPC, the baggage rides in the call’s metadata. For message queues, it rides in the message’s properties or headers. The idea never changes: the propagator writes the entries into the carrier on the way out, and reads them from the carrier on the way in. Only the type of carrier is different.
| Carrier | Where baggage travels |
|---|---|
| HTTP request | The baggage header |
| gRPC call | Call metadata |
| Message queue | Message headers or properties |
| Background job | Job metadata, if you copy it there |
The baggage header travels next to the traceparent header. They are separate headers that are written and read by two different propagators, usually configured together.
7Inside the SDK: Context, API, and Propagator
Let us follow the baggage from the moment code sets it to the moment another service reads it.
sequenceDiagram
participant A as Service A Code
participant B as Baggage API
participant C as Context
participant P as Propagator
participant H as HTTP Client
participant S as Service B
A->>B: Set customer tier to premium
B->>C: New baggage placed in context
A->>H: Call Service B
H->>P: Inject context into headers
P->>C: Read baggage
P-->>H: Add baggage header
H->>S: Request with baggage header
S->>S: Propagator extracts baggage into context
S->>S: Service B code reads customer tier
Code sets a value
Your code asks the baggage API to add an entry. The API builds a new baggage object that includes it.
The context holds it
The new baggage is placed into the current context, so any code running in the same flow can see it.
An outgoing call starts
The instrumented HTTP client calls the propagators, which look inside the context and write the baggage header.
The request travels
The header goes over the network with the request, next to the trace context header.
The receiver extracts
The server instrumentation reads the header and builds a baggage object in the receiving service’s own context.
Code reads it
Any code in the receiving service can now ask the baggage API for an entry, and it will find the value set upstream.
Notice that the baggage API and the propagator are separate parts. The API is the part your code touches, to set and read entries. The propagator is the part that moves the baggage in and out of messages. If the propagator for baggage is not enabled, your code will set values successfully, but nothing will cross the network.
Setting baggage on its own does nothing across services unless the baggage propagator is configured on both sides. The default setup of many SDKs includes it, but check yours.
8Working with Baggage in Code: The Basic Operations
The baggage API is deliberately tiny. Knowing its four basic moves is enough to use it well.
Get an entry
Ask for the value of one key. You receive the value, and perhaps its metadata, or nothing if the key is missing.
Get all entries
Ask for the whole bag, to display it or to copy it somewhere. Useful for debugging.
Set an entry
Add or replace one entry. This builds a new baggage object, and you then place it in the context.
Remove an entry
Take a key out of the bag. The result is a new baggage object without that key.
Making the change take effect
Because baggage is unchangeable, building a new bag is only half the job. The new bag must be placed into the context, and the context must be made current, so that code running after the change, and outgoing calls, see it. Most SDKs provide a way to do this for a specific stretch of code, called a scope. Inside the scope, the new baggage is visible. When the scope ends, the earlier baggage returns.
A traveler at an airport is handed a new luggage tag. Changing the tag on a bag does nothing if the traveler leaves the old bag on the conveyor belt. The new bag must be the one that goes on the plane. In the same way, the new baggage must be the one held by the current context.
Where to set it
The best place to set baggage is at the entrance of the request, for example in the gateway or the first service. Setting it deep in the chain still works, but it reaches only the services that are called after that point, not those that were called earlier.
Removing before you hand over
Sometimes you want to stop an entry from going any further, for example before calling a third-party service. You build a bag without that entry, and use it only for that outgoing call. The entry is not lost for the rest of your own code, if you keep the original bag for other work.
Keep baggage handling in one small helper in your codebase. When many teams read and write baggage in their own ways, it becomes hard to see which keys exist and who owns them.
9Baggage Across Service Borders
The true power of baggage appears when a request crosses many services. Each service can read what the earlier ones wrote, and add its own notes for the later ones.
sequenceDiagram
participant U as User App
participant G as API Gateway
participant O as Order Service
participant P as Payment Service
participant R as Recommendation Service
U->>G: HTTP request
Note over G: Sets customer tier and app version
G->>O: Request with baggage header
Note over O: Reads tier and adds experiment group
O->>P: Request with tier, version, and group
Note over P: Reads tier for risk rules
O->>R: Request with tier, version, and group
Note over R: Reads group to choose an algorithm
P-->>O: Response
R-->>O: Response
O-->>G: Response
G-->>U: Response
Three kinds of actions
Set at the start
The gateway or first service puts in facts only it knows, such as the customer plan and the app version.
Add along the way
A service that makes a decision, such as choosing an experiment group, adds that fact for the services after it.
Read where needed
A downstream service reads the facts it cares about and ignores the rest.
Branches do not mix
When the order service calls both the payment and recommendation services, each call carries its own copy of the baggage. If the payment service adds a new entry, the recommendation service will not see it, because they are separate branches of the request. Baggage flows downward, along the direction of the calls. It does not flow back upward in the response, and it does not flow sideways between sibling calls.
A manager hands the same briefing sheet to two team members who go on separate errands. If one member writes a note on her own copy, the other member does not see it. The sheets were photocopies. Baggage works like photocopies handed down along each branch of a request.
Do not use baggage to send results back to the caller. It travels only in the direction of calls. If a downstream service must send information back, use the response itself.
10Threads, Async Work, and Message Queues
Modern programs do many things at once, and requests often hop through queues. Baggage lives in the context, so it follows the same rules as every other piece of context.
Inside one process
When code hands work to another thread or task, the context must be carried along. If it is not, the new worker starts with an empty context, with no active span and no baggage. Many instrumentation libraries and automatic agents wrap the common thread pools so that the context moves with the work. When you write your own concurrency, check that you pass it on.
Through a message queue
With queues, the sender and receiver do not talk directly. The sender puts a message in, and the receiver takes it out later. The propagator writes the baggage into the message’s headers on the way in, and reads it from the headers on the way out. The receiver can then rebuild the context, even if hours have passed.
flowchart LR
PR["Producer Service sets baggage"] --> IN["Propagator writes baggage into message headers"]
IN --> Q["Message Queue holds the message"]
Q --> OUT["Consumer reads message headers"]
OUT --> EX["Propagator rebuilds baggage in the context"]
EX --> CO["Consumer code reads the entries"]
| Situation | What happens to baggage | What to do |
|---|---|---|
| Same thread | Stays in the context | Nothing special |
| Thread pool or async task | May be lost if not wrapped | Use context-aware wrappers |
| Message queue | Travels only if the propagator writes it into the message | Enable the propagator on both sides |
| Scheduled job started later | Not present, unless saved in the job data | Store needed entries with the job explicitly |
| Retries after a long delay | May carry stale facts | Re-check any fact that could have changed |
Facts in baggage describe the request at the time it started. If a message waits in a queue for hours, a fact such as “customer tier” may have changed. For important decisions, look up the live value.
11Baggage Is Not Telemetry: Getting It into Your Data
This is the point where most beginners are surprised. Putting something in baggage does not make it visible in your traces, logs, or metrics.
The surprise
When a service exports spans, the exporter sends the span’s own fields: IDs, name, times, status, and attributes. It does not look inside the baggage. The baggage stays in the context and in the headers. If you want an entry to appear on telemetry, something must read it from the baggage and write it into the telemetry deliberately.
flowchart LR
CX["Context with baggage"] --> BP["Baggage span processor (optional)"]
BP --> SA["Span attributes"]
SA --> EX["Exporter"]
EX --> BE["Backend: searchable by customer tier"]
CX -. "without a processor" .-> NO["Baggage never reaches the backend"]
Ways to copy baggage into telemetry
A baggage span processor
Several language SDKs offer a ready-made component that copies chosen baggage entries onto every new span as attributes. You usually give it a list of allowed keys.
Read and set in code
At a key point, read an entry from baggage and add it to the current span as an attribute, or to a log record as a field.
A log enrichment step
A logging integration can add selected baggage entries to each log record, so logs can be searched by them.
Selected labels, with care
You can pass a few safe, low-variety entries as metric labels in your own code. Never copy open-ended values.
A passenger’s boarding pass says “window seat”. The airline’s flight report does not automatically list every passenger’s seat. Someone has to copy the seat numbers onto the report if they want to. Baggage is the boarding pass, and telemetry is the flight report. A step must connect them.
Copying all baggage onto every span, without a list of allowed keys, will copy whatever any caller sends, including values you never planned for. Always use an allowed list.
12Using Baggage Across Traces, Logs, and Metrics
Once baggage entries are copied onto telemetry, they become powerful ways to slice and filter your data. Each signal has its own rules about which entries suit it.
| Signal | Good use of baggage entries | Care needed |
|---|---|---|
| Traces | Search slow traces by customer tier, experiment group, or app version | Use an allowed list. Do not copy personal data. |
| Logs | Filter log lines of one tenant or experiment | Make sure log retention rules suit the data |
| Metrics | Split latency by customer tier, if there are only a few tiers | Only low-variety values, or the cost will explode |
The cardinality rule for metrics
Cardinality is the number of different values a label can have. A label with three values, such as free, standard, and premium, creates three series. A label with a million values, such as a user ID, creates a million series. Metrics become slow and expensive very quickly if their labels have many values. So, for metrics, copy only entries with a small and fixed set of possible values. For traces and logs, where each record stands alone, you can afford richer values.
A school can keep one attendance sheet for each class, which is manageable. If it kept a separate sheet for each student for each day, the cupboards would overflow. Labels with few values are class sheets, and labels with endless values are the sheet for each student.
Plan tier
A handful of values. Safe and useful for splitting charts.
Region or app platform
A few dozen values at most. Fine in most systems.
Experiment group or tenant name
Many values, but each record carries only one. Good for search.
User ID, email, session token
Personal or secret, with endless values. Keep them out of baggage and telemetry.
13What Belongs in Baggage, and What Does Not
Because baggage travels everywhere, it deserves careful choices. A good entry is small, safe, stable, and useful to more than one service.
Good fits
Plan or tier
Lets downstream services label telemetry or choose a limit without another lookup.
Experiment or variant name
Lets every service tag its data with the same group, so results can be compared across the whole request.
Client type and version
Tells backend services whether the call came from a phone, a browser, or a partner.
Synthetic or test flag
Marks load-test traffic so that dashboards and alerts can ignore it.
Region or tenant name
Useful in multi-region or multi-tenant systems to filter data without extra lookups.
A debug request marker
Asks all services to log extra detail for this one request, useful in controlled troubleshooting.
Poor fits
| Do not put in baggage | Why |
|---|---|
| Passwords, tokens, API keys, session secrets | They travel in headers to every service and may leak into logs and proxies |
| Personal data such as emails, phone numbers, names | It spreads to many systems and may break privacy rules |
| Large values like documents or lists | Every call carries the extra bytes |
| Anything that grants permission | Callers can forge the header |
| Results to send back upstream | Baggage flows only downward |
| Values that change during the request | Copies on separate branches will disagree |
A useful test: imagine the baggage header printed on a sign above the front door of every company you call. If the content would be awkward on that sign, it does not belong in baggage.
14A Decision Guide: Baggage or Something Else?
Baggage is one tool among several. Use this path of simple questions to decide whether it is the right one.
flowchart TB
S["I have a fact about this request"] --> Q1{"Do other services need it?"}
Q1 -- No --> A1["Span attribute on the current span"]
Q1 -- Yes --> Q2{"Is it safe, small, and not secret?"}
Q2 -- No --> A2["Do not use baggage: look it up securely or pass it in the API"]
Q2 -- Yes --> Q3{"Will services use it for decisions or for telemetry only?"}
Q3 -- Decisions --> A3["Prefer a trusted lookup. Baggage can be forged"]
Q3 -- Telemetry --> A4["Baggage with an allowed list copied to telemetry"]
Reading the guide
Start with the fact. If no other service needs it, a plain span attribute is enough, and nothing has to travel. If other services need it, ask whether it is safe to share, small, and not secret. If it is not safe, find another way, such as a secure lookup. If it is safe, ask what it will be used for. When it is only for labeling telemetry, baggage is a great fit. When it will drive important decisions such as permissions, prefer a trusted source, since anyone can forge a header.
| Need | Best tool |
|---|---|
| Describe one step | Span attribute |
| Describe the service itself | Resource attribute |
| Identify the request in a trace | Trace context |
| Share a safe label with all downstream services | Baggage |
| Pass sensitive or important data between services | The request payload or a secure lookup |
15A Worked Example: Slicing Latency by Customer Tier
Let us walk through a realistic need, step by step, and see exactly where baggage helps.
The need
A shop notices that checkout is sometimes slow, and wonders whether premium customers, who have a promise of fast service, are affected more or less than free customers. Only the gateway knows the customer’s plan. The payment service, where most of the time is spent, does not.
Set at the gateway
When a request arrives and the user is identified, the gateway sets a baggage entry named “customer tier” with a value such as “premium”, “standard”, or “free”.
Travel with the request
The order, payment, and inventory services receive the entry through the baggage header, because the propagators are enabled.
Copy to spans
Each service uses a span processor with an allowed list that contains only “customer tier”. Every span now carries the attribute.
Query the backend
The engineer groups traces by the attribute and compares the duration of the payment span for each tier.
Read the answer
Free customers show a median of 220 milliseconds, standard 230, and premium 410. Premium customers go through an extra risk check. The team now knows where to look.
| Customer tier | Median payment span (ms) | Observation |
|---|---|---|
| Free | 220 | Normal |
| Standard | 230 | Normal |
| Premium | 410 | Slower, caused by an extra risk check |
What baggage saved
Without baggage, the payment service would have to look up the customer plan in a database for each request, or every interface between services would need a new parameter. With baggage, one entry was set once, and every service gained the ability to label its data. The example also shows the three-step discipline: set early, carry on, and copy to telemetry only what you need.
The numbers in the table are an invented example to show the workflow. Your own data will differ, but the process of setting, carrying, copying, and querying stays the same.
16Size, Limits, and Performance
Baggage looks tiny, but it is added to every call. Small amounts, repeated millions of times, become real costs.
The arithmetic of extra bytes
Suppose your baggage holds ten entries of about forty bytes each. That is roughly 400 extra bytes on every request. If your internal services make 100,000 calls per second, that adds up to 40,000,000 bytes, or about 40 megabytes, of extra traffic every second, only for baggage. Every call also spends a bit of time writing and reading the header.
What limits exist
- The W3C standard sets minimum sizes that implementations must support, roughly on the order of dozens of entries and a few kilobytes in total. Implementations may drop entries beyond their limits.
- Web servers, proxies, and gateways have their own header size limits. A very large baggage header can cause a request to be rejected.
- Each hop reads and writes the header again, so a larger bag costs more at every hop.
Few entries
Aim for a handful of entries, not dozens. Every entry costs bytes on every call.
Short keys and values
Use compact names and codes instead of long sentences.
Remove what is no longer needed
If only the first two services need an entry, remove it before calling the rest.
Watch header sizes
Track the size of the baggage header in your gateways and proxies, and alert if it grows.
A single sticker on a suitcase weighs almost nothing. A hundred stickers on every suitcase of a large airline add up to a lot of paper, and slow the handlers. A small bag is a polite bag.
Treat the baggage budget like a team resource. Decide a maximum size, list the allowed keys in a shared document, and review additions as you would review a new database column.
17Security, Part One: Where the Bag Can Leak
The same feature that makes baggage useful, that it goes everywhere, makes it risky. Everything in the bag is sent to every service that the request calls, and that includes services you do not control.
flowchart LR
NET["Public Internet"] --> ED["Edge: validate or strip incoming baggage"]
ED --> IS["Internal Services share baggage"]
IS --> TP["Third-party API: send no baggage"]
IS --> DB["Internal database or queue"]
IS --> LG["Logs and proxies may record headers"]
The places a bag can leak
Third-party calls
If your service calls an outside company, an instrumented client may attach your baggage header to that call. The outsider can now read whatever is in it.
Proxies and logs
Gateways, load balancers, and debugging tools sometimes record request headers. A baggage entry can end up stored in places you never planned.
No encryption by itself
Baggage is readable text. Only the connection, such as encryption in transit, protects it on the wire.
Copy to spans and logs
When baggage is copied onto telemetry, any sensitive value becomes visible to everyone who can view traces and logs.
Calls from front-end code
If browser code adds baggage to its requests, users can see and change it. Never trust or place secrets in baggage that starts in a browser.
Reach grows with the system
Each new service that joins the call chain gets to read the baggage. A harmless entry today may be read by a very different team next year.
How to stop the leaks
- Put only safe, non-secret, non-personal values into baggage. Treat it as public inside your company.
- Configure clients so that baggage and trace headers are sent only to hosts you trust, not to arbitrary outside addresses.
- Use encrypted connections between all services.
- Use an allowed list when copying baggage to telemetry.
- Review proxies and logging settings so that baggage is not recorded where it should not be.
A luggage tag can be read by every porter, every guard, and every curious stranger at every airport. You would write your destination on it, but not your bank PIN. Think of baggage tags as visible to the whole journey.
The most common leak is the outbound call to a third party. Check what headers your HTTP clients send to outside addresses, and stop baggage from leaving your trusted boundary.
18Security, Part Two: Never Trust the Bag You Receive
Leaks are about what goes out. The other half of the story is what comes in. Anyone can send a request with any baggage header they like.
Forged baggage
A caller outside your company can simply add a header such as “customer tier equals premium” to their request. If your services believe it, a free user could receive premium treatment, higher limits, or access to features. Baggage is information that arrived with the request. It is not proof that the information is true.
Fake entries
Attackers can invent entries to unlock behavior that depends on them. Never use baggage alone for permission or pricing decisions.
Huge headers
An attacker can send an enormous baggage header to slow your services or trigger errors. Set limits at the edge.
Junk in telemetry
If you copy incoming baggage to spans, an attacker can fill your data with random keys and values, making searches useless and storage costly.
Unsafe values
A value may contain text meant to confuse tools, such as control characters or very long strings. Treat values as untrusted input.
Protective habits
- Strip or replace at the edge. At the public boundary, remove incoming baggage from unknown callers, and set your own entries from trusted sources, such as your login system.
- Use an allowed list on read. Services read only the keys they expect, and ignore the rest.
- Validate values. Accept only known values, such as the three allowed tiers, and ignore anything else.
- Keep decisions out of baggage. For permissions and billing, look up the real value from a trusted system.
- Cap size. Reject or trim baggage that is too large.
Imagine a nightclub where a guest can show up wearing a wristband that says “VIP”. The doorman does not simply believe it. He checks his own list. Services should treat a baggage entry that says “premium” the same way, as a claim to be checked and not a fact to be trusted.
A simple rule works well: baggage may describe, but it may never authorize. Use it to label and to route telemetry, not to decide who is allowed to do what.
19Monitoring and Governing Your Baggage
Because baggage is invisible by default, it is easy to lose control of it. A little governance keeps it healthy.
Keep an inventory
Maintain a short shared list that records every baggage key in use, who owns it, what it means, what values are allowed, and which services read it. Without this list, nobody knows what is inside the bag, and nobody dares remove anything.
| Key | Owner | Allowed values | Readers | Copied to telemetry? |
|---|---|---|---|---|
| customer.tier | Identity team | free, standard, premium | Payment, recommendations | Spans and metrics |
| experiment.group | Growth team | Group names from the experiment tool | Recommendations, search | Spans and logs |
| client.platform | Mobile team | ios, android, web | All services | Spans and metrics |
| traffic.synthetic | Platform team | true or absent | All services | Spans |
What to measure
Header size
Track the typical and maximum size of the baggage header at gateways. Alert on sudden growth.
Unknown keys
Count how often keys outside the inventory appear. Spikes may indicate misuse or an attack.
Missing expected entries
If a service expects an entry and it is often missing, propagation may be broken somewhere.
Telemetry labels
Watch the number of distinct values of any baggage-derived label in metrics.
Because baggage is not telemetry, you only see its health indirectly, through header sizes at proxies and through the attributes you copy. Plan these measures on purpose, instead of hoping to discover problems.
20Configuration: Turning Baggage On, Off, and Narrow
Baggage works only when the right propagator is set up. A few settings decide whether it flows, and where.
The propagators setting
OpenTelemetry has a standard environment variable named OTEL_PROPAGATORS, which lists the propagators that the SDK should use. A common default is to use both the trace context propagator and the baggage propagator. If baggage is missing from the list, the SDK will not write or read the baggage header, even if your code sets entries.
| Setting | Effect on baggage |
|---|---|
| Trace context and baggage both listed | Both trace identity and baggage travel |
| Only trace context listed | Traces connect, but baggage does not cross services |
| Only baggage listed | Baggage travels, but traces break at service borders |
| No propagators | Nothing crosses service borders |
Narrowing where it goes
- Many HTTP client instrumentations let you limit the hosts that receive propagation headers, so outside addresses get nothing.
- You can configure a gateway or proxy to remove the baggage header from outbound calls to specific destinations.
- You can build the bag for a particular call, leaving out entries that the destination should not see.
Consistency across the fleet
Every service in a call chain needs the same propagator choices. If service A writes baggage but service B does not read it, the entries stop at B and the services after it never see them. Agree on one setup, and apply it through shared configuration, container images, or platform defaults, so each team does not have to remember.
It is like a relay race where every runner must use the same baton shape. If one runner switches to a different baton, the handover fails, and the rest of the team never receives it. Shared configuration keeps the baton the same for everyone.
Include one test in your deployment checks that sends a request with a known baggage entry through your main path, and verifies that the last service sees it.
21Advantages, Disadvantages, and Trade-offs
Baggage is simple and powerful, and it carries real risks. A fair view includes both sides.
Advantages
- Shares facts from the front door with every downstream service, without extra lookups.
- No need to change function signatures or APIs.
- Works across languages, because the format is a public standard.
- Lets all services label telemetry with the same business facts.
- Independent of tracing, so it works even when tracing is off.
- Simple API with four basic moves.
Disadvantages
- Adds bytes and processing to every call.
- Visible to every service in the chain, including outsiders if not controlled.
- Can be forged by any caller, so it cannot be trusted for decisions.
- Does not reach telemetry unless you copy it.
- Lost easily when context is not carried across threads and queues.
- Without governance, becomes a messy shared storage with unclear owners.
Decisions to make on purpose
| Decision | Choose the first when | Choose the second when |
|---|---|---|
| Baggage or lookup in a service | The fact is small, safe, and needed by many services | The fact is sensitive, large, or changes often |
| Copy to all spans or few | You need to search across every service by the label | You only need it at a few key spans, to save cost |
| Strip at the edge or accept | Callers are the public or unknown partners | All callers are inside your trust boundary |
| Baggage or span attribute | Other services must see it | Only the current step matters |
22Design Patterns and Anti-patterns
Teams that use baggage for years find the same good habits, and the same traps.
Patterns worth copying
Set once at the edge
One component, such as the gateway, owns writing the standard entries from trusted sources.
Allowed list on both sides
Only known keys are copied to telemetry, and only known keys are read by services.
Shared helper library
One small library handles setting, reading, validating, and removing entries, so teams do not each invent their own.
Key inventory
A short list shows who owns each key, its meaning, allowed values, and who reads it.
Trim before third parties
Outbound calls to outside services use a clean bag with no entries at all.
Low-variety labels for metrics
Only entries with a few possible values become metric labels.
Anti-patterns to avoid
The Mistake
Using baggage to carry user IDs, emails, or tokens so that “every service knows who the user is”.
Why It Hurts
Personal data and secrets spread to every service, every log that records headers, and every outside system you call. A single leak affects everything.
Do This Instead
Pass identity through a proper authentication token, and let services look up what they need from a trusted system.
The Mistake
Making access or billing decisions based on a baggage entry, such as granting premium limits when baggage says “premium”.
Why It Hurts
Any caller can send that header. The decision can be forged by anyone who knows the key name.
Do This Instead
Decide using a trusted source. Use baggage only for labeling and diagnostics.
The Mistake
Copying every baggage entry onto every span, log, and metric without a list.
Why It Hurts
Telemetry fills with junk and sensitive values, metric costs explode, and a malicious caller can pollute your data at will.
Do This Instead
Copy only a short allowed list of safe keys, and only the low-variety ones to metrics.
23Best Practices and Common Mistakes
Keep this short list close. It prevents most of the trouble that teams meet with baggage.
Decide what is allowed
Write down a short list of keys, with owners and meanings, before the first service uses baggage.
Set at the edge from trusted sources
Create entries where the facts are certain, such as after authentication, and not from incoming headers.
Keep it small
Use few entries with short values, and remove entries once they are no longer useful.
Never store secrets or personal data
Treat the bag as readable by every service and by outsiders if it leaks.
Copy to telemetry deliberately
Use an allowed list and copy only what you will query. Keep only low-variety values on metrics.
Control the exits
Stop baggage from leaving your trust boundary in calls to outside services.
Test the full path
Send a request with a test entry and verify it arrives at the last service, through queues and threads.
Common mistakes and their fixes
| Mistake | What you will see | Fix |
|---|---|---|
| Baggage propagator not enabled | Values set in one service never reach the next | Add it to the propagators setting on both sides |
| New bag built but not made current | Outgoing calls miss the new entry | Place the new bag into the context and make it current |
| Expecting baggage in traces | Searches by the key find nothing | Copy selected entries onto spans |
| Baggage lost across threads | Entries present in some code paths but not others | Carry the context into workers and callbacks |
| Oversized headers | Requests rejected, or entries silently dropped | Trim the bag, and check proxy limits |
| Trusting incoming entries | Wrong behavior, or forged privileges | Validate at the edge, and never decide permissions from baggage |
| Leaking to third parties | Internal facts visible to outsiders | Limit which hosts receive propagation headers |
24Troubleshooting: Why Did My Baggage Not Arrive?
When an entry is missing downstream, follow the path of the header from the point where it was set to the point where it should have been read.
Was it really set?
Check that the code built a new bag, placed it in the context, and made it current before the outgoing call.
Is the propagator enabled on the sender?
Confirm that the propagators setting includes baggage in the sending service.
Is the header on the wire?
Look at a request in a proxy log or a debugging tool, and check that the baggage header is present and not empty.
Did a hop remove it?
Gateways, load balancers, and security tools can strip unknown or oversized headers. Check each hop’s rules.
Is the receiver extracting it?
Confirm that the receiving service has the baggage propagator enabled, and that its server instrumentation is active.
Is the reading code in the right place?
Reading in a background thread that lost the context will show nothing, even if the header arrived.
| Symptom | Likely cause | What to try |
|---|---|---|
| Entry never leaves the first service | Propagator missing, or bag not current | Check the propagators setting and the scope |
| Entry reaches some services but not others | One hop strips the header, or one service has no propagator | Compare headers before and after each hop |
| Entry arrives but reads as empty in some code | Context lost in a thread or callback | Carry the context into the worker |
| Entry present in baggage, absent in traces | Nothing copies it to spans | Add a span processor with an allowed list |
| Entries cut off or missing | Size limits reached | Reduce entries and value lengths |
| Strange values from outside | Incoming baggage not validated | Strip or validate at the edge |
Test with a fake entry that nobody else uses, such as a unique test key, so you can search for it across logs and headers without confusion.
25Real-World and Industry Examples
First, a made-up story that shows baggage at work. Then, a look at the common ways organizations use the same idea.
An illustrative story: the slow free trial
Imagine a video service where users on a free trial complain that videos start slowly. Dashboards show healthy averages, because most users are paying customers. The gateway begins to set a baggage entry for the plan, and every service copies it onto its spans using an allowed list. A week later the team groups the startup span by plan, and sees that free-trial requests wait in a shared queue behind a heavy background job. The fix is a separate queue. Without the entry traveling to every service, the queue service could not have told free requests from paid ones. (This is a made-up example to show the workflow, not a report from a real company.)
Experiments and feature flags
Teams that run experiments often need all services to know which variant a request belongs to, so that each service’s telemetry can be compared fairly. Passing the experiment name in baggage is a common way to do this, using the same standard header across languages.
Marking test and synthetic traffic
Platform teams frequently mark traffic from load tests and health checks, so that dashboards and alerts can ignore it. A baggage entry set at the test tool can reach every service in the path with no code changes in those services beyond reading the label.
Multi-tenant platforms
In systems that serve many customers from the same services, a tenant name carried in baggage lets each service label its telemetry per tenant, helping teams to find which tenant causes load or errors. Because tenant names can be sensitive, access to such telemetry is usually restricted.
The standards behind it
Baggage follows a W3C standard header, which means different tools, languages, and vendors can read and write it in the same way. This is the same approach as the trace context header, and it is why a gateway in one language, a service in another, and a proxy from a third vendor can all take part in carrying the same bag.
26Architecture and Components Involved in Baggage
Baggage is not one single box. It is a small team of parts, each with one clear job. Naming them helps you find the part that failed.
The baggage API
The small interface your code calls to set, read, and remove entries. It holds no network logic.
The context
The invisible package that holds the current bag for the running flow of code, next to the active span.
The propagator
Writes the bag into outgoing messages and rebuilds it from incoming ones, using the standard header format.
Client and server instrumentation
Hooks inside HTTP, gRPC, and messaging libraries that call the propagator automatically at the right moments.
Span or log processors
Optional parts that copy chosen bag entries onto spans and logs, so they appear in your data.
Gateways and proxies
Components at the borders of your system that can validate, trim, or strip baggage headers.
Propagator configuration
The list of enabled propagators, usually set by an environment variable or a startup option.
Receiving code
Any code in a downstream service that asks the API for an entry and acts on it.
Think of an airport baggage system. A traveler fills in the tag (your code and the API). The tag sits on the bag (the context). Conveyor belts and loaders move the bag from plane to plane (propagators and instrumentation). Security staff at the borders may open and check bags (gateways). Each part has one job, and a missing bag usually means one part did not do its job.
When baggage fails, ask which part is missing: the API call, the context, the propagator, the transport hook, or the reading code. The answer is almost always one of these five.
27Data Flow and Lifecycle of a Bag
Every bag has a life: it is made, it travels, it changes, and it ends. Knowing the stages makes it easier to reason about what a given service will see.
Birth at the edge
A request arrives. The first service decides which trusted facts to put in the bag, and builds it.
Activation
The bag is placed in the context and made current, so code and outgoing calls can see it.
Departure
On each outgoing call, the propagator writes the current bag into the message. Each branch receives its own copy.
Arrival
The receiving service extracts the bag into its own context. Its code can read the entries.
Change along the way
A service may add, replace, or remove entries. Because bags are unchangeable, it builds a new bag, and only the calls made from that point onward carry the change.
Optional copying to telemetry
Processors copy allowed entries onto spans and logs, where they live on after the request is over.
End of life
When the request finishes, the context ends and the bag disappears. Nothing is stored unless you copied it to telemetry.
Where the data is stored
The bag itself lives only in memory and in headers while the request is alive. It is not saved to any database by OpenTelemetry. This is good news for privacy, because there is no hidden store. The only lasting copies are the ones you create by copying entries to spans, logs, or metrics, and the ones recorded by proxies or tools that log headers.
Think of baggage as a message in transit. It exists only on the road, and what you keep from it is what you deliberately copy.
28Baggage Together with Sampling, Flags, and Routing
Because baggage sits in the context, other parts of the SDK that look at the context can use it. This opens clever uses, and some warnings.
Sampling decisions
A sampler decides which traces to keep. In several SDKs, the sampler receives the parent context when a span starts. A custom sampler could read a baggage entry such as a debug marker, and decide to always keep traces for requests that carry it. This is useful for a support engineer who wants to see one specific request in full detail. Use it carefully, because anyone who can send the header can force sampling, which raises your telemetry volume and cost.
Feature flags and experiments
A feature flag is a switch that turns a feature on for some users. Many flag systems already provide their own evaluations, so you rarely need baggage to hold the flag itself. But carrying the name of an experiment variant in baggage is a good way to label telemetry. Each service stays free to look up the flag, and the label lets you compare the results across the whole request.
Routing and decisions
Some teams use baggage to hint at routing, such as sending test traffic to a test database. Be careful. Since the header can be forged, a hint in baggage can be misused to reach systems that should not be reachable. Make sure that the final decision is enforced by a trusted component, and treat baggage as a suggestion that is checked.
| Use | Value | Risk | Safeguard |
|---|---|---|---|
| Force sampling of a debug request | See one request in full detail | Callers can inflate your data volume | Allow only from trusted callers, and rate limit |
| Label experiment variant | Compare results across all services | Fake variants pollute data | Validate against known names |
| Mark test traffic | Keep dashboards clean | Real traffic marked as test hides real problems | Set the marker only from trusted tools |
| Route to a test system | Safe testing in production | Misrouted real traffic or access to test data | Enforce the routing in a trusted component |
A luggage tag that says “priority” may speed a bag through the airport. But if anyone could print “priority” tags at home, the airline would need to check them against its own records. A hint in baggage deserves the same quiet check.
Every clever use of baggage for control, such as sampling, routing, or limits, creates a new thing that an attacker can try to flip. For each use, ask what happens if a stranger sets the entry.
29Frequently Asked Questions
Quick answers to the questions people ask most often about baggage.
baggage for HTTP, and through metadata or message headers for gRPC and queues. A propagator writes it on the way out and reads it on the way in.30Summary and Key Takeaways
You started with a suitcase and a few tags. You now know how that picture maps onto requests, services, and the real risks of carrying a bag everywhere.
Key Takeaways
- Baggage is a small bag of labels that travels with a request. It is a set of key and value pairs held in the context, written into messages on the way out, and read back on the way in.
- It is not telemetry. Baggage does not appear on spans, logs, or metrics unless something copies selected entries there. Use an allowed list when you do.
- It is different from its neighbors. Trace context identifies the request, span attributes describe one step, resource attributes describe the service, and baggage shares chosen facts with every downstream service.
- Propagators make it travel. The baggage propagator must be enabled on both sides. In HTTP, the standard
baggageheader carries it, and it flows only downward along the calls. - Keep it small. Every entry is repeated on every call, so a few short entries are enough. Ten small entries on 100,000 calls per second add up to about 40 megabytes per second.
- Treat it as readable by everyone. Never store secrets or personal data, and stop baggage from leaving your trust boundary toward third parties.
- Never trust it for decisions. Any caller can forge baggage. Validate or strip incoming entries at the edge, and keep permissions and billing in trusted systems.