Skip to main content
Version: v2

Creating Component Host Plugins for wasmCloud

Build host plugins as WebAssembly components instead of native Rust code.

A component host plugin is a WebAssembly component that provides a host capability to other workloads. Instead of implementing the HostPlugin trait in Rust and linking it into the host binary, you build the capability as a Wasm component and deploy it into a wasmCloud host at runtime.

Component host plugins are one of two options for extending a host with new capabilities. See Host plugins for the overview of both native and component plugins, and Plugin or Service? for the decision guide.

How it works

At runtime, a component host plugin is a trigger service with a capability ingress. The host pins one long-lived instance of the plugin, and capability calls from other workloads (for example, a wasmcloud:messaging/consumer.publish invocation from a business component in another workload) are dispatched to that pinned instance across a store boundary. See Trigger services for the cross-store dispatch model.

From the caller's perspective this is transparent: a workload does not know whether the capability it imports is implemented as a native plugin or as a component plugin.

Opt-in feature

Component host plugins shipped in wasmCloud 2.6.0, opt-in via the host-component-plugins Cargo feature on wash-runtime. Release images are built with default features only, so running component host plugins requires a host image built with the feature enabled. Check the wash-runtime source for the current shape.

Plugin world

A component host plugin exports the capability it implements and optionally imports wasmcloud:host to observe and cooperate with its runtime environment. A minimal WIT world for a plugin that implements the wasmcloud:messaging/consumer interface (the outbound publish/request side of wasmcloud:messaging) looks like:

wit
package example:messaging-plugin;

world plugin {
  // Import the host awareness interfaces (optional but recommended)
  import wasmcloud:host/identity@0.1.0;
  import wasmcloud:host/cancel@0.1.0;

  // Export the capability this plugin provides
  export wasmcloud:messaging/consumer@0.2.0;
  export wasmcloud:messaging/types@0.2.0;
}

The plugin can additionally export wasi:cli/run@0.3.0 to have a background task that runs alongside inbound invocations — useful when the plugin needs to maintain a long-lived connection, run a scheduler, or process background work. wasi:cli/run is optional; a pure capability plugin can omit it and export only its capability interface. Note that the plugin's world must export at least one capability function; a world that exports only a types interface fails at plugin construction.

The wasmcloud:host interface

The wasmcloud:host@0.1.0 WIT package provides two subinterfaces plugins can import (identity and cancel) and one a plugin can optionally export (workload-lifecycle).

wasmcloud:host/identity

Lets the plugin query who is currently calling it:

  • get-workload-id: returns the workload ID of the caller currently invoking this plugin, or an empty string if there is no in-flight caller (for example, when called from a wasi:cli/run background loop).
  • get-component-id: returns the component ID of the caller currently invoking this plugin, or an empty string when there is no in-flight caller.

These let a plugin partition its in-memory state by caller — for example, keeping one connection or session per workload so that multiple workloads sharing the plugin can't observe or mutate each other's state. The host resolves the current caller per-invocation, so the answer is exact even under concurrent, interleaved calls. A plugin that reads identity from outside a capability handler (e.g., from a background task) should check for the empty-string fallback rather than assuming the caller is known.

wasmcloud:host/cancel

Provides cooperative per-invocation cancellation, backed by opaque host-minted job IDs (u64):

  • current-job: returns the job ID of the invocation currently in flight, or 0 if there isn't one. A handler that expects to be cancellable stashes this ID somewhere reachable (in shared state, keyed by whatever the plugin's protocol lets a canceller name) so another caller can later ask for it to stop.
  • request-cancel: marks a job as cancel-requested. Returns true if the request was accepted, false otherwise — note that this reports acceptance, not completion. The host returns false both when the caller is not in the same workload as the job's owner and when the job id is unknown (already completed, expired, or never issued). A canceller cannot distinguish "wrong workload" from "already done" from the return value alone.
  • is-cancelled: reports whether the current job has been marked for cancellation. Long-running handlers poll this at safe points and unwind cleanly when they observe a true.

Cancellation is cooperative: request-cancel marks the job and the guest chooses when to yield. A well-behaved plugin polls is-cancelled at safe points in long-running work and unwinds cleanly when it observes a signal.

A typical cancellation-aware loop:

rust
use example::plugin::wasmcloud::host::cancel;

// Inside a capability handler
loop {
    if cancel::is_cancelled() {
        // Perform any cleanup, then return
        return Ok(());
    }
    do_one_unit_of_work().await?;
}

wasmcloud:host/workload-lifecycle

Where identity and cancel are imports, workload-lifecycle (added in wasmCloud 2.6.0) is an interface a plugin may optionally export to observe the workloads binding to it:

  • on-workload-bind: invoked before the workload's first capability call reaches the plugin. It receives a workload-info record carrying the workload's ID, name, and namespace, its service and component IDs, and each matched interface instance with its version and manifest-supplied config. Returning an error fails the workload's deployment — use this to reject workloads with invalid configuration, or to provision per-workload state (connections, sessions, credentials) before traffic arrives.
  • on-workload-unbind: a best-effort cleanup notification when the workload stops.

A plugin that doesn't export the interface is unaffected. Note that inside a lifecycle hook, get-workload-id reports the binding workload's ID but get-component-id returns an empty string (there is no in-flight component call) — prefer reading IDs from the workload-info record.

Building the plugin

Component host plugins are built the same way as any other wasmCloud component. Use wash to scaffold a component project, target wasm32-wasip2 (or wasm32-wasip3 if using WASI P3 interfaces such as wasi:http/handler@0.3), and compile.

The output is a standard component binary. Sign and publish it to an OCI registry the same way you distribute any other wasmCloud component.

Deploying the plugin

Component host plugins are declared in host configuration, not in a workload's manifest. A plugin serves every workload on the host that imports its interface, so it's a privileged install the host operator controls. Plugins are loaded once at host start; there is no dynamic reload today.

Every declaration carries a host-unique id (a duplicate ID is a hard error at host startup) and a source (OCI image reference or file path). Optional fields include a digest pin (for supply-chain verification, OCI sources only) and a per-plugin max-restarts override.

The host-component-plugins Cargo feature must be enabled on the target host; a host built without it rejects a plugin declaration at startup with a clear error rather than silently dropping it.

Three loading paths are supported.

wash host

Pass --host-plugin (repeatable) or set WASH_HOST_PLUGINS. Each spec is a comma-separated key=value list; when passing multiple specs via the env var, separate them with ; (a comma would parse as a field delimiter inside a single spec):

shell
wash host \
  --host-plugin id=acme-kv,image=ghcr.io/acme/kv-host:1.0.0,pull=ifNotPresent,max-restarts=3
shell
wash host --host-plugin id=acme-kv,file=./build/kv_plugin.wasm
shell
# Two plugins via the env var — semicolons separate the specs
WASH_HOST_PLUGINS='id=acme-kv,image=ghcr.io/acme/kv-host:1.0.0;id=acme-messaging,file=./build/messaging_plugin.wasm' wash host

Required keys: id, and exactly one of image or file. Optional keys (image sources only): pull (always / ifNotPresent / never; pull-policy is accepted as an alias), digest (OCI digest pin). Optional on either source: max-restarts.

For image sources in an authenticated registry, pass credentials through the environment — WASH_HOST_PLUGIN_REGISTRY_USER and WASH_HOST_PLUGIN_REGISTRY_PASSWORD (or the --host-plugin-registry-user / --host-plugin-registry-password flags). Credentials are deliberately kept out of the --host-plugin spec string so they never appear in process listings or shell history; both must be set together.

wash dev

Add entries under dev.host_plugins in .wash/config.yaml. Fields are camelCased (pullPolicy, maxRestarts, expectedDigest) rather than kebab-cased:

yaml
dev:
  host_plugins:
    - id: acme-kv
      file: ./build/kv_plugin.wasm
      maxRestarts: 3
    - id: acme-messaging
      image: ghcr.io/acme/messaging-host:1.0.0
      pullPolicy: ifNotPresent
      expectedDigest: sha256:…

Helm chart

Declare per host-group under runtime.hostGroups[].hostPlugins; the chart renders these into --host-plugin args on the host Deployment:

yaml
runtime:
  hostGroups:
    - name: default
      hostPlugins:
        - id: acme-kv
          image: ghcr.io/acme/kv-host:1.0.0
          pullPolicy: ifNotPresent
          digest: sha256:…

The Helm YAML uses digest (matching the CLI grammar), not expectedDigest — that camelCased form is only for wash dev's local config file.

After loading

Once a plugin is loaded, workloads that import a WIT interface the plugin exports have their calls routed through the capability ingress to the plugin's pinned instance. Workloads see the plugin as an ordinary provider of the interface — beyond declaring the interface under hostInterfaces as usual, no plugin-specific workload configuration is required.

Supervision knobs

Three environment variables tune the plugin supervisor on the host process:

  • WASH_PLUGIN_STOP_TIMEOUT_SECS (default 5) — how long the plugin's stop routine waits for its supervisor task to exit before aborting it. The host's overall shutdown allows each plugin this budget plus a one-second grace, so the abort path always gets to run.
  • WASH_PLUGIN_HEALTHY_UPTIME_SECS (default 60) — how long a plugin's driver must stay up before a subsequent fault resets the restart budget. Shorter values make the budget more forgiving of transient faults; longer values force the plugin to prove stability before earning fresh restarts.
  • WASH_PLUGIN_RESTART_BACKOFF_MAX_SECS (default 5) — upper bound on the pre-restart backoff between fault and re-instantiation. The supervisor grows the delay after each fault; this caps it.

These are set on the host process (via container env, Helm runtime.env, etc.), not per-plugin.

Considerations

  • Wasm-only capabilities. Component host plugins run inside the sandbox. They cannot open raw sockets, access arbitrary filesystem paths, or reach the host's hardware directly. If your capability needs those, build a native host plugin instead.
  • Design your WIT for cross-store traffic. Resource handles, streams, and futures that cross the store boundary are relocated by the runtime rather than shared. Prefer WIT shapes that minimize resource churn across the boundary — for example, favor value-returning calls over long-lived resource handles when the choice is reasonable.
  • Cooperative cancellation. Long-running handlers should poll wasmcloud:host/cancel.is-cancelled at safe points; otherwise a workload teardown must wait for the natural completion of any in-flight invocation.
  • Runtime execution model. See Trigger services considerations for single-instance concurrency, shared state, and restart-budget notes that apply to every trigger service, including plugins.
  • Restart-budget exhaustion is terminal. If the plugin's supervisor exhausts its restart budget, the plugin stays down for the remainder of the host process's lifetime. Every subsequent capability call routed to it fails; workloads that depend on it lose their capability until the host is restarted. Monitor the plugin's health surface and plan a host-level response (drain and roll the host pod, page an operator) for restart-budget exhaustion — the runtime does not attempt further recovery.

Keep reading