Skip to content

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:

$ docker build --platform linux/amd64 \
    -t your-bsr-instance.example.com/acme/go-json:v1.1.0 .

Write buf.plugin.yaml#

The minimum configuration is the plugin name and version:

buf.plugin.yaml
version: v1
name: your-bsr-instance.example.com/acme/go-json
plugin_version: v1.1.0
  • version is the YAML schema version, always v1.
  • name follows the form BSR_HOST/ORGANIZATION/PLUGIN_NAME and must point at the BSR instance you’re pushing to.
  • plugin_version is v{semver}. The v prefix 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:

Owner  Name     Version  Revision
acme   go-json  v1.1.0   1

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

buf.gen.yaml
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.

  1. Clone the repository:

    $ git clone https://github.com/bufbuild/plugins
    
  2. Open the version of bufbuild/es you want to fork. For example:

    $ cd plugins/bufbuild/es/v2.11.0
    
  3. Edit buf.plugin.yaml:

    • Change name to your plugin’s destination.
    • Add a js_import_style=legacy_commonjs entry under registry.opts.
    • Change registry.npm.import_style from module to commonjs.

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:

$ npm install @buf/acme_petapis.custom-plugins_bufbuild-es@latest
"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.

Delete every version
$ buf beta registry plugin delete your-bsr-instance.example.com/acme/go-json
Delete a specific version
$ 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.