100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Protocol Buffers

gRPC Metadata and Headers

Learn how gRPC carries cross-cutting information like auth tokens and trace IDs via initial and trailing metadata, separate from the protobuf message body.

ImplementationIntermediate8 min readJul 10, 2026
Analogies

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.

go
// 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

Was this page helpful?

Topics covered

#ProtocolBuffers#GRPCStudyNotes#WebDevelopment#GRPCMetadataAndHeaders#GRPC#Metadata#Headers#Sent#StudyNotes#SkillVeris