[Go to site: main page, start]

Skip to content

Generate code with the Buf CLI#

This walkthrough produces Go stubs for a small weather API three different ways: with a locally installed protoc-gen-go binary, with the same plugin run remotely on the Buf Schema Registry (BSR), and with managed mode controlling Go’s go_package option from buf.gen.yaml instead of the .proto file.

Before you start#

  • Install the Buf CLI.
  • Install protoc-gen-go and put it on $PATH. local: plugins are invoked directly; protoc itself isn’t needed for this walkthrough.

    $ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11
    $ export PATH="$PATH:$(go env GOPATH)/bin"
    

Set up the workspace#

A Buf workspace is a directory tree containing one buf.yaml, with one or more modules underneath it.

Create the workspace and a default buf.yaml:

$ mkdir buf-codegen-quickstart && cd buf-codegen-quickstart
$ buf config init

buf config init writes a minimal buf.yaml:

buf.yaml after buf config init
# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml
version: v2
lint:
  use:
    - STANDARD
breaking:
  use:
    - FILE

Add a modules entry pointing at the proto/ directory you’ll create in the next step:

buf.yaml
 # For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml
 version: v2
+modules:
+  - path: proto
 lint:
   use:
     - STANDARD
 breaking:
   use:
     - FILE

For the full set of buf.yaml keys, see the buf.yaml reference.

Add a Protobuf file#

$ mkdir -p proto/acme/weather/v1
$ touch proto/acme/weather/v1/weather.proto
proto/acme/weather/v1/weather.proto
syntax = "proto3";

package acme.weather.v1;

option go_package = "acme/weather/v1";

enum Condition {
  CONDITION_UNSPECIFIED = 0;
  CONDITION_SUNNY = 1;
  CONDITION_RAINY = 2;
}

message GetWeatherRequest {
  float latitude = 1;
  float longitude = 2;
}

message GetWeatherResponse {
  float temperature = 1;
  Condition condition = 2;
}

service WeatherService {
  rpc GetWeather (GetWeatherRequest) returns (GetWeatherResponse);
}

Configure code generation#

Create buf.gen.yaml next to buf.yaml:

buf-codegen-quickstart
├── buf.gen.yaml
├── buf.yaml
└── proto
    └── acme
        └── weather
            └── v1
                └── weather.proto
buf.gen.yaml
version: v2
clean: true
plugins:
  - local: protoc-gen-go
    out: gen/go
    opt: paths=source_relative
inputs:
  - directory: proto

clean: true deletes the previous contents of each out directory before generation runs, so removed .proto types don’t leave behind stale generated files. local: protoc-gen-go runs the binary on your $PATH. For the full configuration surface, see the buf.gen.yaml reference.

Generate with a local plugin#

$ buf generate

A new gen/ directory appears, mirroring the source directory structure:

buf-codegen-quickstart
├── buf.gen.yaml
├── buf.yaml
├── gen
│   └── go
│       └── acme
│           └── weather
│               └── v1
│                   └── weather.pb.go
└── proto
    └── acme
        └── weather
            └── v1
                └── weather.proto

Switch to a remote plugin#

The same protoc-gen-go plugin is hosted on the BSR. Pointing at the BSR copy removes the local install requirement so anyone running buf generate gets the same plugin version without managing their own toolchain.

Remove the previous output and update buf.gen.yaml to use the remote plugin:

$ rm -rf gen
buf.gen.yaml
 version: v2
 clean: true
 plugins:
-  - local: protoc-gen-go
+  - remote: buf.build/protocolbuffers/go:v1.36.11
     out: gen/go
     opt: paths=source_relative
 inputs:
   - directory: proto

Regenerate:

$ buf generate

The output tree is identical to the local-plugin run. For details on the remote-plugin model, see Remote plugins. If the generated code is mainly for downstream consumers rather than this repository, publish the module to the BSR and let consumers install generated SDKs instead.

Enable managed mode#

Managed mode lets buf.gen.yaml set the language-specific file options instead of the producer hard-coding them in every .proto file. Drop the go_package declaration from weather.proto first:

proto/acme/weather/v1/weather.proto
 syntax = "proto3";

 package acme.weather.v1;
-
-option go_package = "acme/weather/v1";

 enum Condition {

Suppose your code lives at github.com/acme/weather, and the generated Go must import as github.com/acme/weather/gen/go/acme/weather/v1. Set go_package_prefix in the managed block:

buf.gen.yaml
 version: v2
 clean: true
+managed:
+  enabled: true
+  override:
+    - file_option: go_package_prefix
+      value: github.com/acme/weather/gen/go
 plugins:
   - remote: buf.build/protocolbuffers/go:v1.36.11
     out: gen/go
     opt: paths=source_relative
 inputs:
   - directory: proto

Run buf generate again. The output directory is unchanged: paths=source_relative keeps files mirroring the source tree.

gen/
└── go
    └── acme
        └── weather
            └── v1
                └── weather.pb.go

What changes is the go_package value the plugin sees. For a versioned package like acme.weather.v1, managed mode produces <prefix>/acme/weather/v1;weatherv1 (the final ;name segment lets the generated Go package use a short name):

What protoc-gen-go sees (the original .proto file is not modified)
syntax = "proto3";

package acme.weather.v1;

option go_package = "github.com/acme/weather/gen/go/acme/weather/v1;weatherv1";

// Messages, enums, services, etc.

The generated Go file imports as github.com/acme/weather/gen/go/acme/weather/v1, matching what the consumer’s import paths expect.

Next steps#