Buf Schema Registry quickstart#
The Buf Schema Registry (BSR) is the missing package manager for Protobuf, allowing you to manage schemas, dependencies, and governance at scale.
You’ll start with a server that has a bug, add a BSR-hosted dependency to validate requests, publish a module so other teams can use it, then consume your API from a client via a generated SDK.
Prerequisites#
- Buf CLI
1.36.0or newer. Runbuf --versionto check. gitand a Go toolchain on your$PATH.- A Buf account.
-
The
buf-examplesrepo cloned locally. Thestartdirectory is where you’ll make changes;finishis the reference to compare against.console $ git clone git@github.com:bufbuild/buf-examples.git $ cd buf-examples/bsr/quickstart/start/server
Throughout the walkthrough, replace <username> with your own BSR username.
Add a Protovalidate dependency#
The quickstart’s InvoiceService has a bug: it accepts invoices with no line items.
The fix is to add Protovalidate and declare a validation rule on the invoice message.
Protovalidate is published as a BSR module, so you can pull it in as a dependency.
Add it to buf.yaml in the server folder:
version: v2
modules:
- path: proto
+deps:
+ - buf.build/bufbuild/protovalidate
lint:
use:
- STANDARD
breaking:
use:
- FILE
Install it and pin a version in buf.lock:
$ buf dep update
WARN Module buf.build/bufbuild/protovalidate is declared in your buf.yaml deps but is unused.
The warning is expected.
You haven’t imported Protovalidate in a .proto file yet.
Import it and add a validation rule that requires at least one line item:
syntax = "proto3";
package invoice.v1;
+import "buf/validate/validate.proto";
import "tag/v1/tag.proto";
// Invoice is a collection of goods or services sold to a customer.
message Invoice {
string invoice_id = 1;
string customer_id = 2;
- repeated LineItem line_items = 3;
+ repeated LineItem line_items = 3 [(buf.validate.field).repeated.min_items = 1];
}
// Code omitted for brevity
See the Protovalidate reference for the full list of rules you can use.
Generate code and test the fix#
The server’s code-generation config lives in buf.gen.yaml.
It’s already set up to emit Go code with a go_package_prefix option applied via managed mode.
Managed mode rewrites Protobuf file options for every .proto file the CLI compiles, which includes your dependencies.
That’s a problem for Protovalidate: its package option would get overwritten, and the generated Go import path would be wrong.
Fix it by disabling the go_package override for that one dependency:
// Code omitted for brevity
managed:
enabled: true
override:
- file_option: go_package_prefix
value: github.com/bufbuild/buf-examples/bsr/quickstart/server/gen
+ disable:
+ - file_option: go_package
+ module: buf.build/bufbuild/protovalidate
Generate code:
A gen directory appears with the generated Go source.
Start the server, which imports the generated stubs:
In a new terminal, try a request with no line items:
$ buf curl \
--data '{ "invoice": { "customer_id": "fake-customer-id" }}' \
--schema . \
--http2-prior-knowledge \
http://localhost:8080/invoice.v1.InvoiceService/CreateInvoice
{
"code": "invalid_argument",
"message": "validation error:\n - invoice.line_items: value must contain at least 1 item(s) [repeated.min_items]",
"details":
// Response omitted for brevity
}
The validation rule rejected the bad request.
The server’s cmd/main.go wires Protovalidate in as a Connect interceptor, which is how the validation runs automatically on every request.
In your own services you’d add the same interceptor; see the Protovalidate documentation for specifics.
Now try a good request:
$ buf curl \
--data '{ "invoice": { "customer_id": "bob", "line_items": [{"unit_price": "999", "quantity": "2"}] }, "tags": { "tag": ["spring-promo","valued-customer"] } }' \
--schema . \
--http2-prior-knowledge \
http://localhost:8080/invoice.v1.InvoiceService/CreateInvoice
{}
Back in the server’s terminal, you’ll see the invoice being created:
2025/03/04 17:21:59 Creating invoice for customer bob for 1998
2025/03/04 17:21:59 - spring-promo
2025/03/04 17:21:59 - valued-customer
Stop the server with Ctrl-c.
The request includes tags that business analysts use to categorize invoices, and the response echoes them back. Other teams at your company could use the same tag types for reporting, campaigns, and analytics. Split them into their own module so others can depend on them without pulling in the full invoice API.
Publish a shared module#
Create an organization to own the new module:
$ buf registry login
$ buf registry organization create buf.build/<username>-quickstart
Created buf.build/<username>-quickstart
buf registry login opens a browser, asks you to approve the token, and writes the credentials to .netrc.
The walkthrough requires an organization; personal user namespaces can’t host BSR modules for this flow.
Create a common repository in the new organization:
$ buf registry module create buf.build/<username>-quickstart/common --visibility public
Created buf.build/<username>-quickstart/common.
Create a sibling directory for the module alongside server:
Initialize the module:
Wire up buf.yaml with the module path and BSR name:
version: v2
+modules:
+ - path: proto
+ name: buf.build/<username>-quickstart/common
lint:
use:
- STANDARD
breaking:
use:
- FILE
Move tag.proto out of the server into the new module:
Build to confirm there are no errors (silent output means success):
Push the module:
$ buf push
buf.build/<username>-quickstart/common:e1fb01dc1bac43ad9b8ca03b7911834c
The commit ID is how other modules will pin to this version.
Add a README so consumers of the module know what it’s for:
# Tags
This module allows you to add custom tags for tracking or analysis.
Push again to upload the README:
Visit https://buf.build/<username>-quickstart/common to see your module’s page.
The BSR auto-generates Protobuf schema documentation from your .proto files and pairs it with the README.
For more on authoring module docs, see Schema documentation.
Depend on the shared module#
Back in server/, refactor to use the new dependency instead of the local tag/ files.
Add it to buf.yaml:
version: v2
modules:
- path: proto
deps:
- buf.build/bufbuild/protovalidate
+ - buf.build/<username>-quickstart/common
lint:
use:
- STANDARD
breaking:
use:
- FILE
Update dependencies:
buf.lock now includes the new dependency, pinned to the commit you just pushed:
# Generated by buf. DO NOT EDIT.
version: v2
deps:
- name: buf.build/bufbuild/protovalidate
commit: d39267d9df8f4053bbac6b956a23169f
digest: b5:c2542c2e9935dd9a7f12ef79f76aa5b53cf1c8312d720da54e03953f27ad952e2b439cbced06e3b4069e466bd9b64019cf9f687243ad51aa5dc2b5f364fac71e
+ - name: buf.build/<username>-quickstart/common
+ commit: ee6cb9c90d16495f82d419d9262dbd27
+ digest: b5:ef7a05bd56d547893a8a2bceaf77860e7051b282120c4f0ed59bc974acf2f57f246e71a691eff52eb069659d6710572baeed26e9e38bb2111318422775805685
Regenerate Go code, update Go module dependencies, and restart the server:
In the other terminal, retry the good request:
$ buf curl \
--data '{ "invoice": { "customer_id": "bob", "line_items": [{"unit_price": "999", "quantity": "2"}] }, "tags": { "tag": ["spring-promo","valued-customer"] } }' \
--schema . \
--http2-prior-knowledge \
http://localhost:8080/invoice.v1.InvoiceService/CreateInvoice
{}
The server works the same way from the outside, but its tags API now comes from a shared module that any team can depend on.
Consume the API with a generated SDK#
Every BSR module gets a set of generated SDKs for the languages the BSR supports.
A team consuming your API imports the SDK through their normal package manager instead of running buf generate against your .proto files.
Publish the invoice module#
The invoice service isn’t published yet.
Publish it the same way you published common.
Create the BSR repository:
$ buf registry module create buf.build/<username>-quickstart/invoice --visibility public
Created buf.build/<username>-quickstart/invoice.
Set the module name in buf.yaml:
version: v2
modules:
- path: proto
+ name: buf.build/<username>-quickstart/invoice
deps:
- buf.build/bufbuild/protovalidate
- buf.build/<username>-quickstart/common
lint:
use:
- STANDARD
breaking:
use:
- FILE
Push:
$ buf push
buf.build/<username>-quickstart/invoice:e1fb01dc1bac43ad9b8ca03b7911834c
Call the API from a client#
Switch to the start/client directory, which has an empty Go module ready for the client implementation.
Install the generated SDKs with go get:
# Base Protobuf types
$ go get buf.build/gen/go/<username>-quickstart/invoice/protocolbuffers/go
# The generated invoice SDK
$ go get buf.build/gen/go/<username>-quickstart/invoice/connectrpc/gosimple
The BSR generates SDKs on first request, so the first go get for a new module takes a few seconds.
Subsequent requests are instant.
Replace client/cmd/main.go with the reference implementation:
package main
import (
tagv1 "buf.build/gen/go/xUSERNAMEx-quickstart/common/protocolbuffers/go/tag/v1"
"buf.build/gen/go/xUSERNAMEx-quickstart/invoice/connectrpc/gosimple/invoice/v1/invoicev1connect"
invoicev1 "buf.build/gen/go/xUSERNAMEx-quickstart/invoice/protocolbuffers/go/invoice/v1"
"context"
"log"
"net/http"
)
func main() {
client := invoicev1connect.NewInvoiceServiceClient(
http.DefaultClient,
"http://localhost:8080",
)
_, err := client.CreateInvoice(
context.Background(),
&invoicev1.CreateInvoiceRequest{
Invoice: &invoicev1.Invoice{
InvoiceId: "invoice-one",
CustomerId: "customer-one",
LineItems: []*invoicev1.LineItem{
{
UnitPrice: 999,
Quantity: 2,
},
},
},
Tags: &tagv1.Tags{
Tag: []string{
"bogo-campaign",
"valued-customer",
},
},
},
)
if err != nil {
log.Fatalf("error creating valid invoice: %v", err)
}
log.Println("Valid invoice created")
}
With the server still running, build and run the client:
$ go mod tidy
$ go run cmd/main.go
2025/03/20 09:58:03 Valid invoice created
The server logs the request:
2025/03/20 09:58:03 Creating invoice for customer customer-one for 1998
2025/03/20 09:58:03 - bogo-campaign
2025/03/20 09:58:03 - valued-customer
The client never ran buf generate.
It pulled strongly-typed Go bindings for your API directly from the BSR.
What’s next#
- Modules and dependency management for more on
buf.yaml,deps, andbuf.lock. - Generated SDKs for every language the BSR supports and how to install them.
- Schema documentation for authoring READMEs and comments that show up in the BSR.