Bazel#
rules_buf is the official Bazel integration for Buf, maintained at bufbuild/rules_buf.
It plugs the Buf CLI’s lint, breaking-change, and format commands into Bazel’s proto_library-based build graph, and ships a Gazelle extension that generates the rules from your buf.yaml files automatically.
This page assumes you’re an existing Bazel user. For an introduction to Buf concepts (modules, workspaces, the BSR), start with the Buf CLI quickstart.
Setup#
Add the following to your MODULE.bazel.
The current versions are listed on Bazel Central Registry; the example below is a tested combination, not a maintained version matrix.
bazel_dep(name = "rules_buf", version = "0.5.2")
bazel_dep(name = "gazelle", version = "0.34.0")
bazel_dep(name = "protobuf", version = "29.1", repo_name = "com_google_protobuf")
bazel_dep(name = "rules_proto", version = "7.0.2")
buf = use_extension("@rules_buf//buf:extensions.bzl", "buf")
# Pin the Buf CLI version that rules_buf uses.
buf.toolchains(version = "v1.71.0")
BSR dependencies#
Declare BSR module dependencies through the same extension, then expose them via use_repo:
buf = use_extension("@rules_buf//buf:extensions.bzl", "buf")
buf.toolchains(version = "v1.71.0")
# Pin each module to a specific commit revision; commits on the BSR are immutable.
buf.dependency(module = "buf.build/envoyproxy/protoc-gen-validate:<commit>")
buf.dependency(module = "buf.build/acme/petapis:<commit>")
# Expose @buf_deps so BUILD files can reference proto_library targets from the dependencies.
use_repo(buf, "buf_deps")
@buf_deps//<dep>:<dep>_proto is then available as a proto_library dep:
load("@rules_proto//proto:defs.bzl", "proto_library")
# Imports "validate/validate.proto"
proto_library(
name = "foo_proto",
srcs = ["pet.proto"],
deps = ["@buf_deps//validate:validate_proto"],
)
Run the Buf CLI through Bazel#
Bazel can run the Buf CLI it downloaded as part of the toolchain so every team member uses the pinned version:
Rules#
The rules sit alongside proto_library rules and read configuration from a buf.yaml file.
Export the buf.yaml with exports_files(["buf.yaml"]) so the rules can reference it.
For most projects, the Gazelle extension generates these rules from your buf.yaml files automatically.
buf_lint_test#
buf_lint_test is a test rule that lints one or more proto_library targets.
Attributes#
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
name |
A unique name for this target. | Name | required | |
config |
The buf.yaml file. |
Label | optional | Applies the default buf.yaml |
targets |
proto_library targets to lint. |
List of labels | required |
Example#
load("@rules_buf//buf:defs.bzl", "buf_lint_test")
load("@rules_proto//proto:defs.bzl", "proto_library")
proto_library(
name = "foo_proto",
srcs = ["pet.proto"],
deps = ["@go_googleapis//google/type:datetime_proto"],
)
buf_lint_test(
name = "foo_proto_lint",
targets = [":foo_proto"],
config = "buf.yaml",
)
The recommended pattern is one buf_lint_test per proto_library target.
The Gazelle extension generates rules in this shape.
buf_breaking_test#
buf_breaking_test is a test rule that checks one or more proto_library targets against an image file representing the previous schema version.
Attributes#
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
name |
A unique name for this target. | Name | required | |
against |
The image file to check against. | Label | required | |
config |
The buf.yaml file. |
Label | optional | Applies the default buf.yaml |
exclude_imports |
Exclude imports from breaking-change detection. | Boolean | optional | False |
limit_to_input_files |
Run breaking checks against only the input files; filters the against image to match. |
Boolean | optional | True |
targets |
proto_library targets to check. |
List of labels | required | [] |
Example#
load("@rules_buf//buf:defs.bzl", "buf_breaking_test")
load("@rules_proto//proto:defs.bzl", "proto_library")
proto_library(
name = "foo_proto",
srcs = ["foo.proto"],
)
buf_breaking_test(
name = "foo_proto_breaking",
against = "//:image.binpb", # The image file to check against.
targets = [":foo_proto"],
config = ":buf.yaml",
)
A single buf_breaking_test per buf.yaml is the recommended granularity, since deletions across the whole module are detectable from a single rule.
For per-target granularity, see module vs package mode below.
Image inputs#
Generate a Buf image file with buf build:
<input> is most often a directory containing a buf.yaml file, but every other input format works too.
The image file is what buf_breaking_test compares against.
Two practical ways to manage it:
- Check it in. Put the image under
testdata/and update it on each release (semver tags work well as the cadence). Simple to reason about, no CI dependency. -
Fetch it from CI artifacts. Build the image on each commit, upload it to object storage, and pull it into the Bazel module via
http_file:MODULE.bazelhttp_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") # Replace ${COMMIT} with the commit you want to compare against. http_file( name = "buf_module", urls = ["https://example.com/images/${COMMIT}/image.binpb"], sha256 = "...", )Reference the resulting label from
buf_breaking_test’sagainstattribute. Update the commit andsha256as the baseline moves.
Maintain a single image per buf.yaml, regardless of whether buf_breaking_test is module-scoped or package-scoped.
buf_format#
buf_format is an executable rule (not a test rule).
Running it formats Protobuf sources in place using the same Buf CLI version pinned by the toolchain:
Gazelle#
Gazelle is a build-file generator for Bazel projects with native Protobuf support.
The rules_buf Gazelle extension generates buf_lint_test and buf_breaking_test rules from your buf.yaml files.
Setup#
Set up rules_buf (Setup), then set up Gazelle following the official instructions.
Modify the root BUILD file to load the Buf extension into a custom Gazelle binary:
-load("@gazelle//:def.bzl", "gazelle")
+load("@gazelle//:def.bzl", "gazelle", "gazelle_binary")
+gazelle_binary(
+ name = "gazelle-buf",
+ languages = [
+ # Native Proto extension.
+ "@gazelle//language/proto",
+ # Buf extension. Must come after the proto language.
+ "@rules_buf//gazelle/buf:buf",
+ ],
+)
gazelle(
name = "gazelle",
+ gazelle = ":gazelle-buf",
)
Export buf.yaml so the rules can reference it: exports_files(["buf.yaml"]).
For workspaces with multiple modules, export each module’s buf.yaml.
Run //:gazelle whenever Protobuf files are added or removed.
Lint#
Gazelle generates a buf_lint_test rule for each proto_library it generates, configured against the buf.yaml for that Protobuf package.
To list the generated rules:
Breaking change detection#
Breaking-change rule generation is opt-in: add a Gazelle directive to the BUILD file at the root of the Buf module (the directory containing buf.yaml):
See Image inputs for how to maintain the image file.
Gazelle generates buf_breaking_test in one of two modes: module mode (the default and recommended) or package mode.
Module mode#
In module mode, Gazelle generates one buf_breaking_test per Buf module, depending on every proto_library in that module.
This mirrors how buf breaking runs on the CLI and is the most accurate way to check for breaking changes; it catches file deletions, since a deleted file’s proto_library disappears from the rule’s targets.
The trade-off: depending on multiple targets from a single test rule is an anti-pattern in Bazel, which is why package mode exists as an alternative.
Package mode#
Add this directive to switch to package mode:
In package mode, Gazelle generates a buf_breaking_test per proto_library rule, so each test runs against only the package that changed.
Worked example: deletion under each mode#
Take a Buf module with two packages:
├── buf.yaml
├── BUILD
├── foo
│ └── v1
│ ├── foo.proto
│ └── BUILD
└── bar
└── v1
├── bar.proto
└── BUILD
If foo.proto is deleted and Gazelle is rerun:
- Module mode: one
buf_breaking_testlives in the rootBUILD. After the delete, itstargetslist losesfoo/v1:foo_proto.bazel test //...then detects the deletion as a breaking change against the image. - Package mode: a
buf_breaking_testlives in each offoo/v1/BUILDandbar/v1/BUILD. After the delete, the rule infoo/v1/BUILDdisappears entirely.bazel test //...has nothing to compare against, and the deletion goes undetected.
Module mode catches deletions; package mode trades that detection for finer-grained per-target tests.
Examples#
The rules_buf repository hosts sample workspaces covering common configurations.
WORKSPACE (legacy)#
rules_buf still supports the legacy WORKSPACE file, but Bazel is phasing WORKSPACE out in favor of the module-based setup (MODULE.bazel).
For new projects, follow Setup above.
For setup steps, the toolchain, BSR dependencies, and the Gazelle extension under WORKSPACE, see the rules_buf README.
A few WORKSPACE-specific surfaces still relevant to existing projects:
buf_dependencies#
buf_dependencies is a repository rule that downloads modules from the BSR and generates build files using Gazelle.
Bazel modules don’t use repository rules directly, so this rule applies only to WORKSPACE projects.
Attributes#
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
name |
A unique name for this repository. | Name | required | |
modules |
Module pins as remote/owner/repo:revision. |
List of strings | required |
Example#
load("@rules_buf//buf:defs.bzl", "buf_dependencies")
buf_dependencies(
name = "buf_deps",
modules = [
"buf.build/envoyproxy/protoc-gen-validate:<commit>",
"buf.build/acme/petapis:<commit>",
],
)
load("@rules_proto//proto:defs.bzl", "proto_library")
# Imports "validate/validate.proto"
proto_library(
name = "foo_proto",
srcs = ["pet.proto"],
deps = ["@buf_deps//validate:validate_proto"],
)
Generating dependencies with Gazelle#
Gazelle can generate buf_dependencies rules from buf.lock files.
Add a gazelle-update-repos target to the BUILD file:
gazelle(
name = "gazelle-update-repos",
args = [
# Can also be `buf.lock`.
"--from_file=buf.yaml",
# Optional: write rules into a .bzl macro instead of WORKSPACE.
"-to_macro=buf_deps.bzl%buf_deps",
# Remove rules that no longer have an equivalent buf.yaml.
"-prune",
],
command = "update-repos",
gazelle = ":gazelle-buf",
)
Add this line anywhere after rules_buf_toolchains in WORKSPACE:
Then run:
This produces buf_deps.bzl with a buf_deps macro that loads the buf_dependencies rules, and calls the macro from WORKSPACE.
Arguments#
-from_file <file>- Required. Must be
buf.yamlorbuf.lock. When set tobuf.yaml, the rule imports from the associatedbuf.lock. -to_macro <macroFile>%<defName>- Optional (default: empty). Tells Gazelle to write new repository rules into a
.bzlmacro instead ofWORKSPACE. -prune true- Optional (default:
False). Whentrue, Gazelle removesbuf_dependenciesrules that no longer have an equivalentbuf.yaml.