What Metadata Is and How It's Sent
gRPC metadata is a collection of key-value pairs, similar in spirit to HTTP headers, that travels alongside an RPC but outside the strongly-typed protobuf message body, making it the standard mechanism for cross-cutting information like auth tokens, request IDs, or client version strings that don't belong in the business payload. Metadata comes in two flavors relative to timing: 'initial' (or 'header') metadata sent before the first message, commonly used for authentication tokens the server needs to check before doing any work, and 'trailing' metadata sent after the handler finishes alongside the final status, useful for information only known once processing completes, such as a server-computed processing-time metric.
Cricket analogy: gRPC metadata is like the official team sheet and toss result announced before a match starts (initial metadata) versus the post-match player-of-the-match announcement made only after the innings conclude (trailing metadata).
Reading and Writing Metadata
On the client, outgoing metadata is attached by creating a metadata map and either passing it directly into the call (Python's stub.SayHello(req, metadata=[('authorization', 'Bearer ' + token)])) or attaching it to the context (Go's metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token)), and on the server, incoming metadata is read from the context via metadata.FromIncomingContext(ctx) in Go or the context.invocation_metadata() call in Python, both returning the key-value pairs the client sent. Because HTTP/2 header names are case-insensitive and metadata keys ending in -bin are automatically base64-encoded for binary-safe transport, developers should stick to lowercase ASCII keys for text values and reserve the -bin suffix specifically for byte-array values like a serialized trace context.
Cricket analogy: Attaching an auth token to outgoing metadata resembles a player's accreditation pass being clipped onto their kit before walking onto the field, checked automatically by ground security without disrupting the game itself.
// Client: attach auth token and request ID as outgoing metadata
func callWithMetadata(ctx context.Context, client pb.GreeterClient, token string) {
md := metadata.Pairs(
"authorization", "Bearer "+token,
"x-request-id", uuid.NewString(),
)
ctx = metadata.NewOutgoingContext(ctx, md)
var header, trailer metadata.MD
resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "Priya"},
grpc.Header(&header), grpc.Trailer(&trailer))
if err != nil {
log.Fatalf("call failed: %v", err)
}
log.Println("response:", resp.Message)
log.Println("server processing time:", trailer.Get("x-processing-time-ms"))
}
// Server: read incoming metadata and set trailing metadata
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
start := time.Now()
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.InvalidArgument, "missing metadata")
}
tokens := md.Get("authorization")
if len(tokens) == 0 || !isValidToken(tokens[0]) {
return nil, status.Error(codes.Unauthenticated, "invalid or missing token")
}
reply := &pb.HelloReply{Message: "Hello, " + req.GetName()}
trailer := metadata.Pairs("x-processing-time-ms",
fmt.Sprintf("%d", time.Since(start).Milliseconds()))
grpc.SetTrailer(ctx, trailer)
return reply, nil
}Common Metadata Use Cases and Interceptor Patterns
The most common production use case for initial metadata is authentication: rather than every handler manually checking a token, an auth interceptor reads authorization from incoming metadata once, validates it, and either aborts with codes.Unauthenticated or attaches the decoded identity to the context for downstream handlers to use via ctx = context.WithValue(...). Metadata is also the standard carrier for distributed tracing context (propagating a trace ID and span ID across service boundaries so a request spanning five microservices can be reconstructed into one trace in a tool like Jaeger) and for client identification metadata like x-client-version, which servers can use to apply compatibility shims or reject dangerously outdated clients.
Cricket analogy: An auth interceptor centralizing token validation resembles a single security checkpoint verifying every player and staff credential once at the stadium gate, rather than each individual dressing room re-checking IDs separately.
Store metadata keys as constants shared between client and server code (or generate them alongside your protos) to avoid typos like 'x-request-id' vs 'x-requestid' silently breaking cross-cutting features such as tracing or auth.
Do not put large payloads or business data in metadata; it is meant for small, cross-cutting key-value pairs. Many gRPC implementations enforce header size limits (commonly around 8KB), and oversized metadata can cause the call to fail outright.
- Metadata is a set of key-value pairs that travels alongside an RPC outside the protobuf message body, similar in purpose to HTTP headers.
- Initial (header) metadata is sent before the first message; trailing metadata is sent after the handler finishes, alongside the final status.
- Clients attach outgoing metadata via the context (Go) or a metadata parameter (Python); servers read it via FromIncomingContext or invocation_metadata().
- Keys ending in -bin are automatically base64-encoded for binary-safe transport; use them only for byte-array values.
- Authentication is the most common metadata use case, typically centralized in an interceptor rather than duplicated in every handler.
- Metadata is the standard carrier for distributed tracing context, letting a trace ID propagate across microservice boundaries.
- Metadata should stay small; it is not meant for bulk business payloads and is often subject to size limits around 8KB.
Practice what you learned
1. What is the key structural difference between gRPC metadata and the protobuf message body of an RPC?
2. What distinguishes trailing metadata from initial (header) metadata?
3. Why does gRPC automatically base64-encode metadata keys that end with the -bin suffix?
4. What is the recommended pattern for validating an authorization token on every RPC in a gRPC server?
5. Why should large business payloads not be sent as gRPC metadata?
Was this page helpful?
You May Also Like
Implementing a gRPC Server
Learn how to define a gRPC service, generate server stubs, implement unary and streaming handlers, and manage a production server's lifecycle safely.
Implementing a gRPC Client
Learn how to establish a gRPC channel, call unary and streaming methods with a generated stub, and configure retries and load balancing for production use.
gRPC Error Handling
Understand gRPC's canonical status code model, how to choose the right code and attach structured details, and how to propagate and recover from errors safely.
gRPC Deadlines and Cancellation
Understand how gRPC deadlines bound RPC execution time, how they propagate across service chains, and how explicit cancellation stops unneeded work.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics