{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/shards/2026/03/what-belongs-in-go-context-values/",
  "description": "A simple litmus test for when to use context values in Go.",
  "path": "/shards/2026/03/what-belongs-in-go-context-values/",
  "publishedAt": "2026-03-17T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Go",
    "API",
    "Distributed Systems"
  ],
  "textContent": "Another common [question] popped up in r/golang:\n\n> I've been reading mixed opinions lately about using context to pass values like request\n> IDs, auth info, or tenant IDs through middleware layers. Some people argue it's fine and\n> exactly what context was extended for after 1.7. Others say it's a code smell that leads\n> to hidden dependencies and untestable code. I see both sides. On one hand it keeps\n> function signatures clean. On the other hand you lose compile-time safety and it's not\n> obvious what a function needs from ctx.\n>\n> Curious how the community here approaches this. Do you use typed getters and setters with\n> context or avoid it entirely in favor of explicit parameters?\n\n---\n\nI think this one is easier to answer. I took a stab at it in a [comment] there. But before\nexpanding on it, the canonical definition of context from the [stdlib doc] helps:\n\n> Package context defines the Context type, which carries deadlines, cancellation signals,\n> and other request-scoped values across API boundaries and between processes.\n\nSo context exists for three things: deadlines, cancellation signals, and request-scoped\nvalues. Anything that doesn't fall into one of those three shouldn't be in a context. The\nfirst two are clear enough. \"Request-scoped values\" is where people get confused.\n\nThere's a simpler litmus test for it. If your code cannot proceed without some value, that\nvalue should not go in a context. All context values must be optional, but not all optional\nvalues belong in context.\n\nYour application can't do much without a user ID or a database connection. Those are hard\ndependencies. Your function needs them to do its job, so they belong in the function\nsignature. On the other hand, your app runs just fine without a trace ID or a request ID.\nNothing breaks if they're missing. That's context territory.\n\nThe [Google Go Style Guide] says the same thing:\n\n> Values of the context.Context type carry security credentials, tracing information,\n> deadlines, and cancellation signals across API and process boundaries.\n\nAnd separately:\n\n> If you have application data to pass around, put it in a parameter, in the receiver, in\n> globals, or in a Context value if it truly belongs there.\n\nNotice what the style guide lists as context-appropriate: security credentials, tracing\ninformation, deadlines, cancellation signals. All cross-cutting infrastructure concerns.\nThey flow through the call chain without affecting what your function actually computes.\n\nWhat belongs in context values:\n\n- Trace IDs, request IDs, correlation IDs\n- Authentication tokens for middleware propagation\n- Logging attributes like request-scoped logger fields\n- Idempotency keys\n\nWhat doesn't:\n\n- User data that your function needs to operate\n- Database connections or service clients\n- Configuration values\n- Business logic inputs\n\nHere are a couple of examples from well-known Go projects.\n\nPrometheus stores query origin metadata in context for [logging]. The engine can execute\nqueries without it. When present, the metadata gets attached to log entries:\n\netcd stores operation [traces] in context. If a trace is present, timing and step data get\nrecorded. If not, a no-op trace is returned and the operation proceeds normally:\n\nBoth follow the same pattern. The context values are infrastructure concerns, not business\ninputs. Prometheus uses it for query observability. etcd uses it for operation tracing.\nNeither changes the outcome of the core operation. The code works correctly with or without\nthe value being present.\n\nSo no, using context for request-scoped values isn't an anti-pattern. It's what context was\ndesigned for. The confusion comes from a loose reading of \"request-scoped.\" Stick to the\nlitmus test: if the function can't work without it, it's a parameter, not a context value.\n\n---\n\n_[Paweł Grzybek] asked whether passing a user ID from auth middleware to a handler through\ncontext violates the litmus test above. The short answer is no. I answered in a [follow-up\nshard]._\n\n\n\n\n[Paweł Grzybek]:\n    https://bsky.app/profile/pawelgrzybek.com\n\n[follow-up shard]:\n    /shards/2026/03/user-id-through-context/\n\n[question]:\n    https://www.reddit.com/r/golang/comments/1rvjpyw/\n\n[comment]:\n    https://www.reddit.com/r/golang/comments/1rvjpyw/comment/oazfcay/\n\n[stdlib doc]:\n    https://pkg.go.dev/context\n\n[Google Go Style Guide]:\n    https://google.github.io/styleguide/go/decisions#contexts\n\n[logging]:\n    https://github.com/prometheus/prometheus/blob/main/promql/engine.go\n\n[traces]:\n    https://github.com/etcd-io/etcd/blob/main/pkg/traceutil/trace.go#L39",
  "title": "What belongs in Go's context values?"
}