Skip to main content
Kratos provides a metadata system for passing key-value pairs between services. Metadata is used for cross-cutting concerns like tracing, authentication, and request context.

Metadata Type

metadata/metadata.go:13
type Metadata map[string][]string
Metadata is a map of string keys to string slice values, similar to HTTP headers.

Creating Metadata

metadata/metadata.go:16-26
import "github.com/go-kratos/kratos/v2/metadata"

// Create new metadata
md := metadata.New(map[string][]string{
    "x-request-id": {"12345"},
    "x-user-id":    {"user-123"},
})

// Create from multiple maps
md := metadata.New(
    map[string][]string{"key1": {"val1"}},
    map[string][]string{"key2": {"val2"}},
)

Metadata Operations

Add

Add a value to a key (creates or appends):
metadata/metadata.go:29-36
md := metadata.New()
md.Add("x-request-id", "12345")
md.Add("x-tags", "tag1")
md.Add("x-tags", "tag2")  // Appends to existing key
// Result: {"x-request-id": ["12345"], "x-tags": ["tag1", "tag2"]}

Get

Get the first value for a key:
metadata/metadata.go:39-45
value := md.Get("x-request-id")  // Returns "12345"
empty := md.Get("nonexistent")   // Returns ""

Set

Set a single value (replaces existing):
metadata/metadata.go:48-53
md.Set("x-request-id", "new-id")
// Replaces all values for the key

Values

Get all values for a key:
metadata/metadata.go:65-67
values := md.Values("x-tags")  // Returns []string{"tag1", "tag2"}

Range

Iterate over all metadata:
metadata/metadata.go:56-62
md.Range(func(key string, values []string) bool {
    log.Infof("%s: %v", key, values)
    return true  // Continue iteration
})

Clone

Create a deep copy:
metadata/metadata.go:70-76
original := metadata.New(map[string][]string{
    "key": {"value"},
})

cloned := original.Clone()
cloned.Set("key", "modified")  // Doesn't affect original

Server Context

Extract metadata from incoming requests:
metadata/metadata.go:81-83
import "github.com/go-kratos/kratos/v2/metadata"

func MyHandler(ctx context.Context, req *pb.Request) (*pb.Response, error) {
    // Extract metadata from context
    if md, ok := metadata.FromServerContext(ctx); ok {
        requestID := md.Get("x-request-id")
        userID := md.Get("x-user-id")
        
        log.Infof("Request: %s, User: %s", requestID, userID)
    }
    
    return &pb.Response{}, nil
}

Store Metadata

metadata/metadata.go:81-83
import "github.com/go-kratos/kratos/v2/metadata"

md := metadata.New(map[string][]string{
    "x-service": {"my-service"},
})

ctx = metadata.NewServerContext(ctx, md)

Client Context

Add metadata to outgoing requests:
metadata/metadata.go:94-96
import "github.com/go-kratos/kratos/v2/metadata"

func CallService(ctx context.Context) error {
    // Create metadata
    md := metadata.New(map[string][]string{
        "x-request-id": {generateRequestID()},
        "x-user-id":    {getUserID()},
    })
    
    // Add to context
    ctx = metadata.NewClientContext(ctx, md)
    
    // Make request - metadata automatically sent
    reply, err := client.GetUser(ctx, &pb.GetUserRequest{
        Id: 123,
    })
    
    return err
}

Append to Context

Add metadata to existing context:
metadata/metadata.go:105-116
import "github.com/go-kratos/kratos/v2/metadata"

// Append key-value pairs
ctx = metadata.AppendToClientContext(ctx, 
    "x-request-id", "12345",
    "x-user-id", "user-123",
)

Merge Metadata

Merge new metadata into context:
metadata/metadata.go:119-126
newMD := metadata.New(map[string][]string{
    "x-trace-id": {"trace-123"},
})

ctx = metadata.MergeToClientContext(ctx, newMD)

HTTP Integration

Metadata automatically maps to HTTP headers:

Server Side

import (
    "github.com/go-kratos/kratos/v2/transport/http"
    "github.com/go-kratos/kratos/v2/middleware/metadata"
)

// Add metadata middleware
httpSrv := http.NewServer(
    http.Middleware(
        metadata.Server(),  // Extracts headers into metadata
    ),
)

// Access in handler
func HandleRequest(ctx http.Context) error {
    if md, ok := metadata.FromServerContext(ctx); ok {
        userAgent := md.Get("user-agent")
        authorization := md.Get("authorization")
    }
    return nil
}

Client Side

import (
    "github.com/go-kratos/kratos/v2/transport/http"
    "github.com/go-kratos/kratos/v2/middleware/metadata"
)

client, _ := http.NewClient(
    ctx,
    http.WithMiddleware(
        metadata.Client(),  // Injects metadata as headers
    ),
)

// Add metadata
ctx = metadata.AppendToClientContext(ctx,
    "x-api-key", "secret-key",
)

// Make request - metadata sent as headers
var reply Response
client.Invoke(ctx, "GET", "/api/resource", nil, &reply)

gRPC Integration

Metadata maps to gRPC metadata:

Server Side

import (
    "github.com/go-kratos/kratos/v2/transport/grpc"
    "github.com/go-kratos/kratos/v2/middleware/metadata"
)

grpcSrv := grpc.NewServer(
    grpc.Middleware(
        metadata.Server(),  // Extracts gRPC metadata
    ),
)

func (s *Service) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    if md, ok := metadata.FromServerContext(ctx); ok {
        traceID := md.Get("x-trace-id")
    }
    return &pb.User{}, nil
}

Client Side

import (
    "github.com/go-kratos/kratos/v2/transport/grpc"
    "github.com/go-kratos/kratos/v2/middleware/metadata"
)

conn, _ := grpc.DialInsecure(
    ctx,
    grpc.WithMiddleware(
        metadata.Client(),  // Injects as gRPC metadata
    ),
)

client := pb.NewUserServiceClient(conn)

ctx = metadata.AppendToClientContext(ctx,
    "x-trace-id", "trace-123",
)

reply, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 1})

Common Use Cases

Request ID Propagation

import "github.com/google/uuid"

func RequestIDMiddleware() middleware.Middleware {
    return func(handler middleware.Handler) middleware.Handler {
        return func(ctx context.Context, req interface{}) (interface{}, error) {
            md, _ := metadata.FromServerContext(ctx)
            
            requestID := md.Get("x-request-id")
            if requestID == "" {
                requestID = uuid.New().String()
            }
            
            // Add to outgoing requests
            ctx = metadata.AppendToClientContext(ctx,
                "x-request-id", requestID,
            )
            
            return handler(ctx, req)
        }
    }
}

User Authentication

func AuthMiddleware() middleware.Middleware {
    return func(handler middleware.Handler) middleware.Handler {
        return func(ctx context.Context, req interface{}) (interface{}, error) {
            md, _ := metadata.FromServerContext(ctx)
            
            token := md.Get("authorization")
            userID, err := validateToken(token)
            if err != nil {
                return nil, errors.Unauthorized("INVALID_TOKEN", "invalid token")
            }
            
            // Propagate user ID
            ctx = metadata.AppendToClientContext(ctx,
                "x-user-id", userID,
            )
            
            return handler(ctx, req)
        }
    }
}

Trace Context Propagation

func TracingMiddleware() middleware.Middleware {
    return func(handler middleware.Handler) middleware.Handler {
        return func(ctx context.Context, req interface{}) (interface{}, error) {
            md, _ := metadata.FromServerContext(ctx)
            
            traceID := md.Get("x-trace-id")
            spanID := md.Get("x-span-id")
            
            if traceID == "" {
                traceID = generateTraceID()
            }
            
            // Propagate to downstream services
            ctx = metadata.AppendToClientContext(ctx,
                "x-trace-id", traceID,
                "x-span-id", generateSpanID(),
                "x-parent-span-id", spanID,
            )
            
            return handler(ctx, req)
        }
    }
}

Best Practices

Always use the metadata middleware for automatic header/metadata conversion.
Metadata keys are automatically lowercased. Use lowercase keys consistently.
Use prefixes like x- for custom metadata keys to avoid conflicts.
Metadata is meant for small pieces of data. Don’t store large payloads.
Only propagate metadata that’s needed downstream. Don’t blindly forward everything.
Always validate metadata values before using them, especially for security-sensitive data.

Complete Example

package main

import (
    "context"
    "github.com/go-kratos/kratos/v2/middleware"
    "github.com/go-kratos/kratos/v2/middleware/metadata"
    "github.com/go-kratos/kratos/v2/transport/http"
    "github.com/go-kratos/kratos/v2/transport/grpc"
    "github.com/google/uuid"
)

// Middleware to ensure request ID
func RequestID() middleware.Middleware {
    return func(handler middleware.Handler) middleware.Handler {
        return func(ctx context.Context, req interface{}) (interface{}, error) {
            md, _ := metadata.FromServerContext(ctx)
            
            requestID := md.Get("x-request-id")
            if requestID == "" {
                requestID = uuid.New().String()
                md.Set("x-request-id", requestID)
            }
            
            // Propagate to downstream
            ctx = metadata.NewClientContext(ctx, md)
            
            return handler(ctx, req)
        }
    }
}

func main() {
    // HTTP Server
    httpSrv := http.NewServer(
        http.Middleware(
            metadata.Server(),  // Extract HTTP headers
            RequestID(),        // Ensure request ID
        ),
    )
    
    // gRPC Server
    grpcSrv := grpc.NewServer(
        grpc.Middleware(
            metadata.Server(),  // Extract gRPC metadata
            RequestID(),        // Ensure request ID
        ),
    )
    
    // HTTP Client
    httpClient, _ := http.NewClient(
        context.Background(),
        http.WithMiddleware(
            metadata.Client(),  // Inject as HTTP headers
        ),
    )
    
    // gRPC Client
    grpcConn, _ := grpc.DialInsecure(
        context.Background(),
        grpc.WithMiddleware(
            metadata.Client(),  // Inject as gRPC metadata
        ),
    )
}

HTTP Transport

HTTP header handling

gRPC Transport

gRPC metadata handling

Middleware

Metadata middleware

Transport

Transport headers

Build docs developers (and LLMs) love