Descriptors
Descriptors are Protobuf schemas represented as Protobuf data. They power reflection, dynamic decoding, validation, and compiler plugin input.
Schemas Describing Schemas
When you compile a schema, the compiler doesn't just generate code. It can also output a binary representation of the schema itself, called a FileDescriptorSet. Both compilers will produce one: buf build -o descriptor.binpb or protoc --descriptor_set_out.
This FileDescriptorSet is itself a Protobuf message. Google defines a schema (descriptor.proto) that describes how to represent .proto files. This means you can use Protobuf tools to read and analyze Protobuf schemas dynamically at runtime. Buf's descriptors reference is a deep dive into how they work.
Why is this useful?
Dynamic Decoding
Tools like this web explorer use descriptors to decode arbitrary binary data without generating static code.
Validation
Complex rule engines (like protovalidate) use descriptors to apply constraints dynamically.
Code Generation
Code generator plugins receive these descriptors as input. This is how every custom generator is built, and why the same plugin binary works under both buf generate and protoc.
Server Reflection
A server can hand out its own descriptors over the wire, so a client can call it knowing nothing in advance. buf curl uses this to invoke any reflection-enabled gRPC or Connect endpoint with no .proto files on your disk.
Try editing the schema below to see how the generated FileDescriptorSet changes in real-time.
edition = "2023"; package demo.v1; import "buf/validate/validate.proto"; message User { string id = 1 [json_name = "uid"]; string name = 2; uint32 age = 3 [(buf.validate.field).uint32.lt = 150]; Role role = 4; enum Role { ROLE_UNSPECIFIED = 0; ROLE_USER = 1; ROLE_ADMIN = 2; } }