Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
25 Jun 2025 · 6 min read ·Article 17 / 110
Go

17 Adding Metadata to a gRPC Request

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

gRPC has become the de facto standard for service-to-service communication in many companies that adopt microservices. Its efficiency makes it a great fit for large-scale systems. In practice, however, the need for more “contextual” communication often arises—for example, sending an authentication token, a trace id, or a custom header with every request. This is where metadata in gRPC becomes important: it provides an extra channel for sending out-of-band data without modifying the core protocol definition.

In this article, I’ll cover gRPC request metadata thoroughly—from the basic theory and an architectural walkthrough to code examples in Node.js and Go. Beyond the examples, I’ll also share best practices and the pitfalls you need to watch out for. Let’s get started!


What Is Metadata in gRPC?

Much like HTTP headers, metadata in gRPC is a set of key-value pairs sent as part of every RPC request or response. They are used for additional information such as:

  • Authentication tokens
  • Trace/Correlation IDs for observability
  • Content options
  • Tenant info (multi-tenant apps)
  • And any other external information you don’t want to ‘pollute’ the main payload with

Diagram of the Metadata Flow in gRPC

Let’s visualize the flow with the following mermaid diagram:

MERMAID
sequenceDiagram
    participant Client
    participant Network
    participant Server

    Client->>Network: gRPC Request (Metadata + Payload)
    Network->>Server: gRPC Request (Metadata + Payload)
    Server-->>Network: gRPC Response (Metadata + Payload)
    Network-->>Client: gRPC Response (Metadata + Payload)

As you can see, both the request and the response can carry metadata.


A Real-World Use Case: The “User Authentication” Scenario

One of the most common scenarios is passing a JWT (JSON Web Token) on every request as proof of authentication. In gRPC, this is not done through a protocol parameter, but via headers/metadata.


Example Protobuf Schema

Suppose we have the following service:

proto
 1// protos/user.proto
 2syntax = "proto3";
 3
 4service UserService {
 5    rpc GetProfile(Empty) returns (UserProfile);
 6}
 7
 8message Empty {}
 9
10message UserProfile {
11    string user_id = 1;
12    string name = 2;
13    string email = 3;
14}

There is no auth token field, because it will be sent through metadata.


Implementing gRPC Metadata

Let’s look at the implementation in two popular languages:

1. Node.js (using @grpc/grpc-js)

a) Client: Sending a Request Along With Metadata

js
 1const grpc = require('@grpc/grpc-js');
 2const protoLoader = require('@grpc/proto-loader');
 3
 4// Load proto
 5const packageDefinition = protoLoader.loadSync('./protos/user.proto');
 6const userProto = grpc.loadPackageDefinition(packageDefinition).UserService;
 7
 8// Create the client
 9const client = new userProto('localhost:50051', grpc.credentials.createInsecure());
10
11// Create the metadata
12const metadata = new grpc.Metadata();
13metadata.add('authorization', 'Bearer eyJhbGciOiJI...');  // a JWT, for example
14
15// Call the RPC with metadata
16client.GetProfile({}, metadata, (err, response) => {
17  if (err) console.error(err);
18  else console.log(response);
19});

b) Server: Reading the Request Metadata

js
 1const grpc = require('@grpc/grpc-js');
 2
 3function getProfile(call, callback) {
 4  // Get the 'authorization' value
 5  const authToken = call.metadata.get('authorization')[0];
 6  console.log('Received JWT:', authToken);
 7
 8  // TODO: Verify the token
 9  // Simulate a user profile
10  if (authToken) {
11    callback(null, {
12      user_id: 'u-123',
13      name: 'Rizky',
14      email: 'rizky@example.com'
15    });
16  } else {
17    callback({
18      code: grpc.status.UNAUTHENTICATED,
19      message: 'Auth token required'
20    });
21  }
22}

2. Go: Server & Client

a) Client: Sending Metadata

go
 1import (
 2    "context"
 3    "google.golang.org/grpc"
 4    "google.golang.org/grpc/metadata"
 5)
 6
 7func main() {
 8    conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
 9    defer conn.Close()
10    client := pb.NewUserServiceClient(conn)
11
12    // Create the metadata
13    md := metadata.New(map[string]string{"authorization": "Bearer abcd1234"})
14    ctx := metadata.NewOutgoingContext(context.Background(), md)
15
16    // RPC call with metadata
17    resp, err := client.GetProfile(ctx, &pb.Empty{})
18    // Handle resp / err
19}

b) Server Handler: Reading Metadata

go
 1import (
 2    "context"
 3    "google.golang.org/grpc/metadata"
 4)
 5
 6func (s *server) GetProfile(ctx context.Context, req *pb.Empty) (*pb.UserProfile, error) {
 7    md, ok := metadata.FromIncomingContext(ctx)
 8    if !ok {
 9        return nil, status.Error(codes.Unauthenticated, "No metadata found")
10    }
11    tokens := md["authorization"]
12    if len(tokens) == 0 {
13        return nil, status.Error(codes.Unauthenticated, "No authorization token")
14    }
15    // Verify the token, etc.
16    return &pb.UserProfile{
17        UserId: "u-456",
18        Name: "Dewi",
19        Email: "dewi@example.com",
20    }, nil
21}

Metadata Types: Default vs Custom

Metadata KeyExampleDescription
authorizationBearer eyJhbG…JWT/OAuth2 token
x-trace-id3f4a…Correlation for tracing
localeid-IDMulti-language customization
content-typeapplication/grpcUsually managed automatically

Notes:

  • Standard keys are usually all lowercase.
  • Any “binary” value (not a string) must be given the -bin suffix.

Best Practices for Using Metadata

1. Don’t Overload Metadata

Metadata is very useful, but make sure it stays small—ideally tens to hundreds of bytes. Metadata that is too large can degrade performance and break the semantics of the service.

2. Standardize Keys

Use consistent naming and document the keys used across the entire team/service.

3. Keep It One-Way at Minimum

Use different metadata for requests and responses in line with the gRPC standard.
For example, client authentication goes from client -> server, while error details go in the response.

4. Don’t Store Important State

Metadata is stateless. Don’t use it to store information that should live in a database or session.

5. Interceptor/Middleware

Implement validation, logging, or metadata injection through an interceptor/middleware. It’s modular and scalable.


Interceptor: Inject Automatically on Every Request (Node.js Example)

js
 1function authInterceptor(options, nextCall) {
 2  return new grpc.InterceptingCall(nextCall(options), {
 3    start: function(metadata, listener, next) {
 4      // Inject the token on every request
 5      metadata.add('authorization', 'Bearer juragan_secret');
 6      next(metadata, listener);
 7    }
 8  });
 9}
10
11// Use it on the client
12const client = new userProto('localhost:50051', grpc.credentials.createInsecure(), {
13  interceptors: [authInterceptor]
14});

Common Troubleshooting

  • Metadata not reaching the server: Check the key spelling and the interceptor implementation, and keep in mind that oversized metadata may be rejected by middleware/network.
  • Metadata lost in a stream (bidirectional): Some implementations need to pay attention to initial vs trailing metadata.
  • Binary header errors: Keys with the -bin suffix are required for buffer/binary data; otherwise you’ll run into parsing errors.

Conclusion

With a solid understanding and the best practices around metadata, your gRPC services can be more secure, traceable, and future-proof. Service-to-service communication that carries context is now a reality without having to “break” the protocol—all it takes is a touch of metadata.

Don’t forget to test edge-case scenarios and build a utility/interceptor for consistency in how metadata is sent and processed. Metadata isn’t just a “header”; it’s also a key to interoperability and observability in modern distributed systems.

Other Sources & References:


Happy coding—may your service grow ever more robust and scalable! 🚀

Related Articles

💬 Comments