Custom plugins#
A custom plugin is a Protobuf plugin you build, package as a Docker image, and push to your private BSR instance.
Once pushed, the plugin shows up alongside Buf-managed plugins for use in buf.gen.yaml and, if its buf.plugin.yaml declares registry metadata, in generated SDKs.
Custom plugins are available on the Pro plan and above. For setup help, contact Support or your Buf representative.
You can push as many custom plugins as you need. Each plugin is either public (every user with access to the instance can use it) or private (only members of the owning organization). Custom plugins appear in the plugin catalog filters under their declared language; plugins without a language assignment land in the Other category.
Plugin protocol requirements#
Plugins that access the file system, make network requests, or otherwise produce a CodeGeneratorResponse that depends on anything outside the CodeGeneratorRequest don’t conform to the Protobuf plugin protocol and aren’t supported on the BSR.
A non-conforming plugin may work on your local machine, but it won’t work reliably as a remote plugin or with generated SDKs, and Buf can’t help debug those failures.
Build and push a plugin#
The walkthrough below uses protoc-gen-go-json as the running example.
The push command (buf beta registry plugin push) is still under the beta namespace; expect it to graduate to buf registry plugin push in a future release.
Prerequisites#
- Docker installed and running.
- A BSR organization on your private instance to publish under. See Manage organizations for setup.
- The user pushing the plugin needs the Admin role or higher on that organization.
Build the Docker image#
# syntax=docker/dockerfile:1.6
FROM golang:1.24-bookworm AS builder
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go install -ldflags "-s -w" -trimpath github.com/mitchellh/protoc-gen-go-json@v1.1.0
# When building a Docker image on a host that doesn't match linux/amd64 (such as an
# Apple Silicon machine), go install puts the binary in $GOPATH/bin/$GOOS_$GOARCH/.
# Move it to /go/bin so the next stage can copy it from a stable path.
RUN mv /go/bin/linux_amd64/* /go/bin || true
FROM scratch
COPY --from=builder --link /etc/passwd /etc/passwd
COPY --from=builder /go/bin/ /
USER nobody
ENTRYPOINT [ "/protoc-gen-go-json" ]
A few build-side rules:
- Target
linux/amd64. The BSR’s plugin executor runs Linux/amd64; an ARM-only image won’t work. - Use a minimal base image such as scratch or distroless, with multi-stage builds so build tooling doesn’t ship in the runtime image.
- Run as a non-root user (the example uses
nobody); make sure that user exists in the runtime image.
Build and tag the image:
Write buf.plugin.yaml#
The minimum configuration is the plugin name and version:
versionis the YAML schema version, alwaysv1.namefollows the formBSR_HOST/ORGANIZATION/PLUGIN_NAMEand must point at the BSR instance you’re pushing to.plugin_versionisv{semver}. Thevprefix is required, and the rest must be valid semantic versioning.
For source URL, integration guide URL, language assignment, registry metadata for generated SDKs, and dependency declarations, see the buf.plugin.yaml reference.
Push the plugin#
Authenticate to the BSR (see Authentication) and run:
$ buf beta registry plugin push \
--visibility=public \
--image=your-bsr-instance.example.com/acme/go-json:v1.1.0
--image references an image that’s already locally available; pull from Docker Hub or another registry first if the image lives elsewhere.
--visibility=private restricts the plugin to members of the owning organization.
A successful push prints the plugin coordinates:
The plugin is immediately available in the organization’s Plugins section.
Use a custom plugin#
A pushed custom plugin behaves like any other plugin on the BSR; how consumers reach it depends on what the plugin’s buf.plugin.yaml declares.
As a remote plugin in buf.gen.yaml (see Generate code with remote plugins):
version: v2
plugins:
- remote: your-bsr-instance.example.com/acme/go-json:v1.1.0
out: gen/go
If the plugin’s buf.plugin.yaml declares registry metadata for a language ecosystem (npm, Cargo, Maven, etc.), the BSR also publishes generated SDKs for it.
Consumers install those SDKs through the language’s normal package manager; see the SDK customization example below for one full setup.
Customize a Buf-managed plugin for generated SDKs#
A common reason to push a custom plugin is to change the registry-metadata defaults that determine how a generated SDK is shaped, since the BSR can’t override those defaults at install time.
The example below forks bufbuild/es (which emits ESM by default) into a copy that emits CommonJS instead.
The same pattern works for any plugin you want to fork.
The example uses a private instance at your-bsr-instance.example.com with an organization named custom-plugins.
Clone and modify the base plugin#
The Buf-managed plugins are publicly accessible source.
Start from the existing plugin’s directory and adjust buf.plugin.yaml.
For an authoritative field list, see bufbuild/plugins/CONTRIBUTING.md.
-
Clone the repository:
-
Open the version of
bufbuild/esyou want to fork. For example: -
Edit
buf.plugin.yaml:- Change
nameto your plugin’s destination. - Add a
js_import_style=legacy_commonjsentry underregistry.opts. - Change
registry.npm.import_stylefrommoduletocommonjs.
- Change
The patch:
--- A/plugins/bufbuild/es/v2.11.0/buf.plugin.yaml
+++ B/plugins/bufbuild/es/v2.11.0/buf.plugin.yaml
@@ -1,5 +1,5 @@
version: v1
+name: your-bsr-instance.example.com/custom-plugins/bufbuild-es
-name: buf.build/bufbuild/es
plugin_version: v2.11.0
source_url: https://github.com/bufbuild/protobuf-es
integration_guide_url: https://github.com/bufbuild/protobuf-es#quickstart
@@ -8,8 +8,10 @@
- javascript
- typescript
registry:
+ opts:
+ - import_extension=js
+ - js_import_style=legacy_commonjs
npm:
+ import_style: commonjs
- import_style: module
rewrite_import_path_suffix: pb.js
deps:
- package: '@bufbuild/protobuf'
Build and push the forked plugin#
Build for linux/amd64:
$ docker build \
--platform linux/amd64 \
-t your-bsr-instance.example.com/custom-plugins/bufbuild-es:v2.11.0 .
Push with --override-remote.
The flag rewrites the BSR host in the plugin’s name field and in any dependency references at push time; use it when the buf.plugin.yaml you’re pushing was authored against a different host (here, buf.build):
$ buf beta registry plugin push \
--visibility=public \
--image=your-bsr-instance.example.com/custom-plugins/bufbuild-es:v2.11.0 \
--override-remote=your-bsr-instance.example.com
If the configuration you push already names the target remote, you don’t need --override-remote.
Consume the forked plugin#
Once pushed, the BSR generates SDKs from the forked plugin like any other. Installing one returns CommonJS instead of ESM:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const { proto3 } = require("@bufbuild/protobuf");
const { Money } = require("@buf/googleapis_googleapis.custom-plugins_bufbuild-es/google/type/money_pb.js");
Delete a custom plugin#
The Buf CLI deletes a plugin by reference, with or without a version.
Warning
Without a version, every version of the plugin is deleted.
$ buf beta registry plugin delete your-bsr-instance.example.com/acme/go-json:v1.1.0
Delete requires the Admin role on the plugin. The BSR rejects the delete if other plugins still declare a dependency on the target; remove the dependents first or pin them to an alternative.