Skip to content

Code Generation

A .proto file does nothing on its own. You run it through protoc, the Protocol Buffers compiler, which parses the schema and then hands it to plugins that emit code for a specific language. protoc itself knows nothing about Go or Python — the plugins do.

For gRPC there are usually two outputs:

  • Message code — the structs/classes for User, GetUserRequest, etc. (from the base protobuf plugin).
  • Service code — the client stub and the server interface (from the gRPC plugin).
flowchart LR
  proto["user.proto"] --> protoc["protoc
(parses schema)"]
  protoc --> base["protobuf plugin
→ message types"]
  protoc --> grpc["gRPC plugin
→ stub + server interface"]
protoc parses; plugins emit language code

Each language has its own plugin pair. Here is the typical command in each ecosystem:

Terminal window
# install the two plugins once
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
user/v1/user.proto
# → user.pb.go (messages) + user_grpc.pb.go (stub + server interface)

Whatever the language, the generated code always contains the same three things:

  1. Message types — a typed object for every message, with getters/setters or fields and the serialization logic baked in.
  2. A client stub — a class with one method per rpc, so the client just calls stub.GetUser(req).
  3. A server interface — an abstract base (Go interface, Python servicer, Node service definition) with one method per rpc for you to implement.

You never edit generated files. When the .proto changes, you regenerate.

Raw protoc commands get unwieldy fast — plugin paths, include paths, output options, multiplied across languages. buf is the modern tool that replaces them. You declare inputs and outputs in a small config file and run one command:

buf.gen.yaml
version: v2
plugins:
- remote: buf.build/protocolbuffers/go
out: gen
- remote: buf.build/grpc/go
out: gen
Terminal window
buf generate

buf also lints your schemas and detects breaking changes (covered in the Ecosystem module) — which is why most teams standardize on it.

What is the role of protoc versus its plugins?
What three artifacts does gRPC code generation produce?
What should you do when the .proto changes?
Why do most teams use buf instead of raw protoc commands?