> For the complete documentation index, see [llms.txt](https://docs.roadrunner.dev/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.roadrunner.dev/docs/grpc/protoreg.md).

# Protoreg

The `protoreg` plugin provides a shared protobuf registry. It reads `.proto` definitions for [gRPC server reflection](#server-reflection), custom interceptors, and dynamic message handling. The RoadRunner v3 bundle includes this plugin.

## Features

* Parses and registers `.proto` files at startup
* Provides a shared `protoregistry` accessible by other plugins
* Supports multiple import paths for proto dependencies
* Automatically resolves and registers proto file dependencies
* Exposes service and method descriptors for reflection-based operations
* Thread-safe registry access

## Use Cases

* **Custom gRPC Interceptors**: Build interceptors that need to inspect or modify messages based on their proto definitions
* **Dynamic Message Handling**: Deserialize and serialize protobuf messages without compile-time generated code
* **Request/Response Validation**: Validate gRPC requests against proto schemas
* **Logging & Monitoring**: Create detailed logs with field-level message inspection

## Configuration

Both `grpc` and `protoreg` configuration sections are required to start the plugin. Keep the service definitions in both sections consistent.

The plugin is configured under the `protoreg` section in your `.rr.yaml` configuration file:

```yaml
version: '3'

grpc:
  listen: "tcp://127.0.0.1:9001"
  proto:
    - "proto/serviceapis/service/v1/service.proto"
    - "proto/serviceapis/user/v1/user.proto"

protoreg:
  proto_path:
    - proto/commonapis
    - proto/serviceapis
  files:
    - service/v1/service.proto
    - user/v1/user.proto
```

Set `server.command` to your gRPC worker as shown in the [gRPC configuration](/docs/grpc/grpc.md#usage).

### Configuration Options

#### `proto_path` (required)

A list of directories where the plugin will search for proto files and their dependencies. These paths work [similarly to the `--proto-path`/`-I` flag in `protoc`](https://www.mankier.com/1/protoc#--proto_path).

**Why it's needed**: Proto files often import other proto files (e.g., `import "google/protobuf/timestamp.proto"`). The import paths tell the parser where to find these dependencies. You typically need:

* A path containing your service definitions
* A path containing shared/common message definitions
* Paths to any third-party proto files (like Google's well-known types)

```yaml
proto_path:
  - proto/commonapis     # Shared message definitions
  - proto/serviceapis    # Service-specific proto definitions
  - proto/third_party    # External dependencies (e.g., google/api)
```

#### `files` (required)

A list of proto files to parse and register. Paths are relative to the `proto_path` directories. Use explicit file names. This list does not expand the glob patterns supported by `grpc.proto`.

Both `proto_path` and `files` must contain at least one entry. Blank entries fail initialization. Each file must exist under at least one configured import path.

**Why it's needed**: This explicitly declares which proto files define the services and messages you want to access at runtime. The plugin will:

1. Parse each listed proto file
2. Automatically resolve and register all imported dependencies
3. Build a registry of all services and their methods

```yaml
files:
  - service/v1/service.proto    # Your gRPC service definitions
  - user/v1/user.proto          # Additional service definitions
```

## Server reflection

RR automatically uses the `protoreg` registry for server reflection. Configure the same service files in `grpc.proto` and `protoreg.files`. Paths in `protoreg.files` are relative to `proto_path`; they do not replace `grpc.proto`.

For the [Hello World service](/docs/grpc/grpc.md#protoc-plugin), use:

{% code title=".rr.yaml" %}

```yaml
grpc:
  listen: "tcp://127.0.0.1:9001"
  proto:
    - "proto/helloworld.proto"

protoreg:
  proto_path:
    - "proto"
  files:
    - "helloworld.proto"
```

{% endcode %}

Reflection clients can retrieve file and message descriptors for the PHP service. For the preceding listener without TLS, use `grpcurl` without a local `.proto` file:

```bash
grpcurl -plaintext 127.0.0.1:9001 describe helloworld.Greeter
grpcurl -plaintext -d '{"name":"RoadRunner"}' 127.0.0.1:9001 helloworld.Greeter/SayHello
```

Reflection streams do not call unary authentication interceptors. See [reflection access controls](/docs/grpc/grpc.md#server-reflection).

## Example: Project Structure

A typical project structure when using this plugin:

```
my-project/
├── .rr.yaml
├── proto/
│   ├── commonapis/
│   │   └── common/
│   │       └── v1/
│   │           └── message.proto
│   └── serviceapis/
│       └── service/
│           └── v1/
│               └── service.proto
└── ...
```

**proto/commonapis/common/v1/message.proto**:

```protobuf
syntax = "proto3";

package common.v1;

message Request {
  string id = 1;
  string payload = 2;
}

message Response {
  string id = 1;
  string result = 2;
}
```

**proto/serviceapis/service/v1/service.proto**:

```protobuf
syntax = "proto3";

package service.v1;

import "common/v1/message.proto";

service MyService {
  rpc Process (common.v1.Request) returns (common.v1.Response) {}
  rpc Ping (common.v1.Request) returns (common.v1.Response) {}
}
```

**Configuration (.rr.yaml)**:

```yaml
version: '3'

grpc:
  listen: "tcp://127.0.0.1:9001"
  proto:
    - "proto/serviceapis/service/v1/service.proto"

protoreg:
  proto_path:
    - proto/commonapis
    - proto/serviceapis
  files:
    - service/v1/service.proto
```

## Using the Registry in Your Plugin

Declare `protoreg.Registry` as an initialization dependency:

```go
package myplugin

import "github.com/roadrunner-server/protoreg/v6"

type Plugin struct {
    registry protoreg.Registry
}

func (p *Plugin) Init(registry protoreg.Registry) error {
    p.registry = registry
    return nil
}
```

### Registry Interface

The interface uses `protoresolve.Registry` from `github.com/jhump/protoreflect/v2/protoresolve` and descriptors from `github.com/jhump/protoreflect/desc`:

```go
type Registry interface {
    // Registry returns the parsed descriptors.
    Registry() *protoresolve.Registry

    // Services maps fully qualified service names to descriptors.
    Services() map[string]*desc.ServiceDescriptor

    // The path format is "/package.Service/Method" or "package.Service/Method".
    FindMethodByFullPath(method string) (*desc.MethodDescriptor, error)
}
```

## Example: gRPC Interceptor

This unary interceptor logs method descriptors without logging request bodies. Include it with `grpc` and `protoreg` in your custom build. Set `grpc.interceptors: ["descriptor-logger"]` to activate it.

```go
package interceptor

import (
    "context"
    "log/slog"

    "github.com/roadrunner-server/protoreg/v6"
    "google.golang.org/grpc"
)

type Logger interface {
    NamedLogger(name string) *slog.Logger
}

type Plugin struct {
    registry protoreg.Registry
    log *slog.Logger
}

func (p *Plugin) Init(registry protoreg.Registry, logger Logger) error {
    p.registry = registry
    p.log = logger.NamedLogger(p.Name())
    return nil
}

func (p *Plugin) Name() string {
    return "descriptor-logger"
}

func (p *Plugin) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
        method, err := p.registry.FindMethodByFullPath(info.FullMethod)
        if err == nil && method != nil {
            p.log.Info("grpc method",
                "method", method.GetFullyQualifiedName(),
                "input_type", method.GetInputType().GetFullyQualifiedName(),
                "output_type", method.GetOutputType().GetFullyQualifiedName(),
            )
        }
        return handler(ctx, req)
    }
}
```

## Troubleshooting

### "proto path does not exist"

Ensure all paths in `proto_path` exist and are accessible. Paths can be absolute or relative to the RoadRunner working directory.

### "proto file does not exist"

The proto file must exist in one of the configured `proto_path`. Check that:

1. The file path in `files` is relative to an import path, not an absolute path
2. The directory structure matches the import path

### "file already registered"

This occurs when the same proto file is included multiple times (e.g., listed in `files` and also imported by another file). The plugin handles dependencies automatically, so you typically only need to list your top-level service definitions in the `files` section.

## License

MIT License - see LICENSE file for details.
