Reflection API#
The Buf Reflection API downloads a FileDescriptorSet for any module on the BSR over Connect, so a runtime tool can decode Protobuf bytes without static bindings for the messages it sees.
Note
The Reflection API at buf.reflect.v1beta1 is still in beta and may change.
For a stable RPC surface that returns the same kind of payload, see buf.registry.module.v1.FileDescriptorSetService.
$ curl \
https://buf.build/buf.reflect.v1beta1.FileDescriptorSetService/GetFileDescriptorSet \
-H "Content-Type: application/json" \
-d '{"module": "buf.build/connectrpc/eliza"}'
The response is a FileDescriptorSet that describes every file in the requested module, along with every imported file the module’s types transitively depend on: every service, message, field, enum, and option.
Why reflection#
Without a schema, Protobuf binary data can’t be decoded meaningfully: fields are identified by tag numbers, and the wire format reuses encoding strategies across types, so a sequence of bytes alone can’t tell you whether a value is a string, a binary blob, or a nested message. Three places where that matters and code typically can’t depend on the schema at compile time:
- Inspecting RPC traffic. Tools like
tcpdump, Wireshark, and proxies need a schema to render encoded payloads as something a human can read. - Inspecting stored payloads. Messages on a queue or in a durable store may have been produced over a long stretch of time, against many versions of an evolving schema.
- Data-pipeline transforms. A pipeline that validates blobs as a known type, drops sensitive fields, or converts to JSON for a downstream sink needs the schema for whatever message it’s looking at, fresh enough to know about new fields.
For an end-to-end Go library that wraps the API for these use cases, see Prototransform.
Call the API#
The API ships as a single Connect service:
| Service | buf.reflect.v1beta1.FileDescriptorSetService |
|---|---|
| Method | GetFileDescriptorSet |
| Module reference | buf.build/bufbuild/reflect |
| GitHub | bufbuild/reflect-api |
| Reference docs | buf.reflect.v1beta1.FileDescriptorSetService |
Generate a Connect or gRPC client from buf.build/bufbuild/reflect, install one of its generated SDKs, or call the service over plain HTTP+JSON as in the curl example above.
For full HTTP request shape options, see Calling BSR APIs.
The module field is the BSR module name in the form REMOTE/OWNER/REPOSITORY.
For example, buf.build/connectrpc/eliza resolves on the public BSR (buf.build) under the connectrpc organization, repository eliza.
version is optional; omitted, it returns the latest commit on the module’s default label.
The same value forms accepted by deps: in buf.yaml work here: a commit ID, a label name (default label is main), or the empty value for latest-on-default.
For the full picture, see Dependency management.
Authentication#
Public modules don’t require a BSR token; the example above works without one.
For private modules, supply a token via Authorization: Bearer ${BUF_TOKEN}; see Authentication.
Filter by symbol#
For large modules where you only need part of the schema, the symbols field prunes the returned FileDescriptorSet to a specific service, message, or other named entity (and the dependencies it transitively references).
Pass an array of fully qualified names.
$ curl \
https://buf.build/buf.reflect.v1beta1.FileDescriptorSetService/GetFileDescriptorSet \
-H "Content-Type: application/json" \
-d '{
"module": "buf.build/googleapis/googleapis",
"version": "75b4300737fb4efca0831636be94e517",
"symbols": ["google.longrunning.Operations"]
}'
Symbol filtering can shrink the response by roughly an order of magnitude. The example call above returns a FileDescriptorSet around 11 KB; the same call without the symbols field returns about 10x that for googleapis/googleapis.
What you do with a FileDescriptorSet#
A FileDescriptorSet is the runtime equivalent of generated code: build a “rich” descriptor index from it, then construct dynamic messages backed by those descriptors instead of by static types.
A dynamic message can marshal and parse Protobuf bytes the same as a generated message, but its fields are resolved through the descriptor at runtime, which lets one program handle messages of any type.
For Go, Prototransform wraps the watcher, descriptor, dynamic-message, and conversion plumbing into one library. For other languages, the underlying primitives are part of the official Protobuf runtimes:
| Language | Descriptors | Dynamic messages |
|---|---|---|
| C++ | google::protobuf::Descriptor |
google::protobuf::DynamicMessage |
| Go | protoreflect |
dynamicpb |
| Java | com.google.protobuf.Descriptors |
com.google.protobuf.DynamicMessage |
| Python | descriptor_pool |
message_factory |
C# and PHP expose descriptor reflection but don’t ship dynamic-message support; for a Wireshark- or pipeline-style appliance, those runtimes need a third-party library or a different language.
For background on the descriptor model itself, see the Descriptors reference.
Further reading#
- Prototransform: Go library that wraps the Reflection API with a schema watcher, format converter, and filter pipeline.
- Download a module’s FileDescriptorSet: the stable v1 RPC and
curlshape for the same payload. buf.build/bufbuild/reflect: module and Generated SDKs for the Reflection API.bufbuild/reflect-api: source repository.