Skip to content

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.

MODULE.bazel
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:

MODULE.bazel
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:

BUILD
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:

MODULE.bazel
use_repo(buf, "rules_buf_toolchains")
$ bazel run @rules_buf_toolchains//:buf -- --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",
)
$ bazel test :foo_proto_lint

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",
)
$ bazel test :foo_proto_breaking

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:

$ buf build --exclude-imports -o image.binpb <input>

<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.bazel
    http_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’s against attribute. Update the commit and sha256 as 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:

BUILD
load("@rules_buf//buf:defs.bzl", "buf_format")

buf_format(
    name = "buf_format",
)
$ bazel run :buf_format

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:

BUILD
-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.

$ bazel run //:gazelle

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:

$ bazel query 'kind(buf_lint_test, //...)'

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):

BUILD
# gazelle:buf_breaking_against //:against_image_file

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.

$ bazel run //:gazelle
$ bazel query 'kind(buf_breaking_test, //...)'

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:

# gazelle:buf_breaking_mode package

In package mode, Gazelle generates a buf_breaking_test per proto_library rule, so each test runs against only the package that changed.

$ bazel run //:gazelle

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_test lives in the root BUILD. After the delete, its targets list loses foo/v1:foo_proto. bazel test //... then detects the deletion as a breaking change against the image.
  • Package mode: a buf_breaking_test lives in each of foo/v1/BUILD and bar/v1/BUILD. After the delete, the rule in foo/v1/BUILD disappears 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#

WORKSPACE
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>",
    ],
)
BUILD
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:

BUILD
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:

WORKSPACE
load("@rules_buf//buf:defs.bzl", "buf_dependencies")

Then run:

$ bazel run //:gazelle-update-repos

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.yaml or buf.lock. When set to buf.yaml, the rule imports from the associated buf.lock.
-to_macro <macroFile>%<defName>
Optional (default: empty). Tells Gazelle to write new repository rules into a .bzl macro instead of WORKSPACE.
-prune true
Optional (default: False). When true, Gazelle removes buf_dependencies rules that no longer have an equivalent buf.yaml.