Skip to main content

Plugins

EXPERIMENTAL

Plugins are an experimental feature. The manifest, the Go PDK, and the ABI may change outside of major version releases.

Overview​

Bento's WebAssembly (WASM) plugin system allows you to dynamically extend the component library with your own custom processors. Instead of recompiling the core binary, you can define processors as standalone modules that load directly at runtime.

A Bento plugin consists of two files within a dedicated directory:

my-plugin/
β”œβ”€β”€ plugin.yaml # Plugin manifest (required)
└── plugin.wasm # Compiled WASM binary (required)

Load the plugin with -p (or --plugin):

bento -p ./my-plugin -c config.yaml

The -p flag can be used multiple times to load multiple plugins.

Configure it as a processor in config.yaml. The processor name must match name in the manifest:

pipeline:
processors:
- my-plugin: {}

Pass the same -p flag to commands that read the config, such as test and lint:

bento -p ./my-plugin test config.yaml
bento -p ./my-plugin lint config.yaml

See Unit Testing for writing tests.

Why WebAssembly (WASM)?​

While custom builds in Go give you the full plugin API at the cost of compiling and maintaining your own binary, templates require no build step at all -- but can only combine existing components.

Bento WASM plugins sit between the two: they introduce custom logic while running on any stock Bento binary. WebAssembly makes this possible through three guarantees:

  • Portability. Once compiled, a WASM module runs on every platform Bento supports, so it can be distributed as a single package rather than built per target machine.
  • Isolation. Modules run in a sandbox with no access to host resources unless explicitly granted at instantiation. Because each module is confined to its own dedicated linear memory, a fatal plugin crash or OOM won't take down the main Bento process.
  • Language flexibility. You aren't limited to Goβ€”any language that compiles to a 32-bit WASM target can implement the ABI.

This isolation requires copying messages into and out of the plugin's memory on each call. Plugins can only be processors, and they can't use Bento resources such as caches. Use a custom build when you need another component type or access to those resources.

Inspect manifests before loading

Because host permissions are configured in plugin.yaml, always audit a third-party plugin's runtime.wasm settings before loading it:

  • Filesystem access: Check for mounted directories and write permissions. A plugin can follow symlinks inside a mounted directory to targets outside it.
  • Memory limits: Without memory.max_pages, each instance can allocate up to 4 GiB, and concurrent calls can spawn multiple instances.

See the Sandbox in the Plugin ABI guide for full details on runtime capabilities.

Enabling the Plugin Runtime​

The plugin runtime is enabled in the released bento binary from v1.24 onwards.

To enable it for custom builds, import the public/plugin/wazero_v1_beta package:

import _ "github.com/warpstreamlabs/bento/public/plugin/wazero_v1_beta"

Once enabled, a custom build can also register a plugin via service.RegisterWasmPlugin:

//go:embed plugin/plugin.yaml
var manifest string

//go:embed plugin/plugin.wasm
var wasm []byte

func init() {
if err := service.RegisterWasmPlugin(manifest, wasm); err != nil {
panic(err)
}
}

Language Support​

Bento provides a plugin development kit (PDK) for Go, which handles the ABI for you. Plugins in other languages implement the ABI directly, which is more work and more likely to break between releases. The Rust examples show how.

Examples​

Complete plugins are available at public/wasm/examples:

ExampleLanguageDescription
uppercaseGoUppercases each message and prepends a configurable prefix.
reverseGoReverses a batch of messages, and records their positions.
rust_uppercaseRustUppercases each message, implementing the ABI without a PDK.
rust_csvRustParses CSV messages into JSON, implementing the ABI without a PDK.

Next Steps​