Developing Plugins
Build native and WebAssembly plugins with the Rust SDK: runtimes, decoration, batching, typed actions, and local testing.
Developing plugins
This is the short development guide. The canonical architecture and Plugin Platform handbook contains a complete native plugin implementation, WebAssembly conversion, SDK reference, package lifecycle, testing matrix, performance guidance, and troubleshooting.
Rust is the maintained SDK language for lla 0.6.0. Native plugins and Rust
WebAssembly components use the same high-level Plugin trait. Other languages
may generate Component Model bindings from the published WIT world.
Choose a runtime
Use a native plugin when you need an existing native dependency, unrestricted platform integration, or the smallest possible host overhead. Native libraries are trusted code and must be built for every supported operating system and architecture.
Use a WebAssembly component when portability and enforced permissions matter.
The host must be compiled with --features wasm-plugins; official Linux,
macOS, and Windows release binaries enable it. The embedded runtime is
available on supported x86_64 and ARM64 Linux/macOS/Windows builds. i686 builds
report WebAssembly packages as unsupported, while the official NetBSD amd64
build omits Wasmtime entirely.
Create a native plugin
Create a library crate and configure it as a dynamic library:
[package]
name = "my-plugin"
version = "1.0.0"
edition = "2021"
[lib]
name = "my_plugin"
crate-type = ["cdylib"]
[dependencies]
lla_plugin_sdk = "0.6"While developing inside this repository, use the workspace dependency instead:
[dependencies]
lla_plugin_sdk.workspace = trueImplement only the capabilities the plugin needs:
use lla_plugin_sdk::{interface::proto, Plugin};
#[derive(Default)]
struct MyPlugin;
impl Plugin for MyPlugin {
fn decorate_entry(
&mut self,
mut entry: proto::DecoratedEntry,
) -> proto::DecoratedEntry {
entry.custom_fields.insert("owner".into(), "example".into());
entry
}
}
lla_plugin_sdk::export_plugin!(MyPlugin);The exported plugin type must implement Default + Send + 'static. The export
macro validates and embeds the crate-root plugin.toml, generates the API v3
entrypoint, contains panics, and ensures plugin-allocated responses are freed by
the plugin.
Decoration and batching
decorate_entry handles one entry. The default decorate_batch implementation
calls it once for each entry. Override decorate_batch when the plugin can
share I/O or computation across a batch:
fn decorate_batch(
&mut self,
mut entries: Vec<proto::DecoratedEntry>,
_format: &str,
) -> Vec<proto::DecoratedEntry> {
let shared_state = load_shared_state_once();
for entry in &mut entries {
decorate_with_state(entry, &shared_state);
}
entries
}The host sends at most 512 entries per batch.
Typed actions
Declare each action in plugin.toml; the manifest is the public contract. The
host parses and validates arguments before calling the plugin.
[[actions]]
id = "inspect"
description = "Inspect a path"
examples = ["lla plugin run my_plugin inspect -- README.md --limit 10"]
interactive = false
arguments = [
{ name = "path", type = "path", position = 0, required = true },
{ name = "limit", type = "integer", option = "--limit", default = 10, min = 1, max = 100 },
]
output = { type = "value" }Implement registered_actions with matching IDs and run_action to receive a
HashMap<String, TypedValue>. Return an ActionResponse containing none,
text, value, or typed table output. Do not print machine-readable output
directly; the host renders the declared output format.
Installation and plugin doctor reject missing handlers and manifest/output
contract mismatches.
Create a Rust WebAssembly component
Enable the component feature and export with the component macro:
[lib]
name = "my_plugin"
crate-type = ["cdylib"]
[dependencies]
lla_plugin_sdk = { version = "0.6", features = ["component"] }use lla_plugin_sdk::Plugin;
#[derive(Default)]
struct MyPlugin;
impl Plugin for MyPlugin {}
lla_plugin_sdk::export_component!(MyPlugin);Set runtime = "wasm-component" and use the generated .wasm filename as the
manifest entrypoint. Then build it:
rustup target add wasm32-wasip2
cargo build --release --target wasm32-wasip2The maintained interface is
sdk/wit/lla-plugin.wit.
Install and test locally
cargo build --release
lla install --dir /path/to/my-plugin
lla plugin doctor
lla plugin info my_plugin
lla plugin run my_plugin inspect -- README.md
lla plugin run my_plugin inspect --output json -- README.mdWorkspace maintainers can validate an entire release bundle with:
./scripts/build_plugins.sh --target "$(rustc -vV | sed -n 's/^host: //p')"
./scripts/verify_plugins_v3.sh dist/plugins-<os>-<arch>Use the SDK fixtures as executable examples: