| name | extism-wasm-plugins |
| description | Extism plugin system — multi-language WASM plugins (Rust/Go/JS/Python/C) with PDK, host functions, shared memory protocol. Build extensible agent tools where each plugin is an isolated WASM module. Sources: extism/extism (BSD-3-Clause). |
/extism-wasm-plugins
When to Use
- Building a plugin system where third-party code extends your agent
- Each plugin must be isolated (WASM sandbox) but able to call host functions
- Multi-language plugins: Rust, Go, JavaScript, Python, C all compile to the same interface
- Agent tool registry backed by hot-loadable WASM plugins
Do NOT use for
- Single-language native addons (use [[napi-rs-native-addons]])
- Sandboxing untrusted code with filesystem access (use [[wasmtime-wasi-sandbox]])
- Browser-side WASM (use [[wasm-bindgen-js-interop]])
Architecture
Host (Node.js / Rust / Go / Python)
├─ Plugin manager
│ ├─ plugin-a.wasm (Rust PDK)
│ ├─ plugin-b.wasm (Go PDK)
│ └─ plugin-c.wasm (JS PDK)
└─ Host functions exposed to all plugins
├─ kv_get / kv_set — shared KV store
├─ http_request — proxied HTTP (host controls allowed URLs)
└─ log — host-side logging
Shared memory protocol:
Host writes input → plugin_input_length / plugin_input_load
Plugin reads input → plugin_get_input
Plugin writes output → plugin_set_output
Host reads output → plugin_output_length / plugin_output_load
Node.js host (calling plugins)
import { Plugin, ExtismContext } from '@extism/extism';
const ctx = new ExtismContext();
const plugin = await ctx.plugin('./plugins/summarizer.wasm', {
functions: {
'extism:host/user': {
kv_read: (plugin, offset) => {
const key = plugin.readString(offset);
const val = kvStore.get(key) ?? '';
return plugin.writeString(val);
},
kv_write: (plugin, keyOffset, valOffset) => {
const key = plugin.readString(keyOffset);
const val = plugin.readString(valOffset);
kvStore.set(key, val);
},
}
},
memory: { max: 5 },
});
const input = JSON.stringify({ text: "Long article..." });
const output = await plugin.call('summarize', input);
console.log(output.string());
plugin.free();
ctx.free();
Rust plugin (PDK)
use extism_pdk::*;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct Input { text: String }
#[derive(Serialize)]
struct Output { summary: String, word_count: usize }
#[plugin_fn]
pub fn summarize(input: Json<Input>) -> FnResult<Json<Output>> {
let words: Vec<&str> = input.text.split_whitespace().collect();
let summary = words.iter().take(20).cloned().collect::<Vec<_>>().join(" ");
Ok(Json(Output {
summary: format!("{}...", summary),
word_count: words.len(),
}))
}
#[host_fn]
extern "ExtismHost" {
fn kv_read(key: &str) -> String;
fn kv_write(key: &str, value: &);
}
(input: Json<Input>) FnResult<Json<Output>> {
= (, &input.text[..]);
{
(cached) = (&cache_key) {
!cached.() {
((Output { summary: cached, word_count: }));
}
}
}
((Output { summary: .(), word_count: }))
}
Go plugin (PDK)
package main
import (
"encoding/json"
extism "github.com/extism/go-pdk"
)
func summarize() int32 {
input := extism.InputString()
var data map[string]string
json.Unmarshal([]byte(input), &data)
result := map[string]string{
"summary": data["text"][:min(len(data["text"]), 100)] + "...",
}
out, _ := json.Marshal(result)
extism.OutputBytes(out)
return 0
}
func main() {}
Plugin registry (hot-reload)
class PluginRegistry {
private ctx = new ExtismContext();
private plugins = new Map<string, Plugin>();
async load(name: string, wasmPath: string): Promise<void> {
if (this.plugins.has(name)) this.plugins.get(name)!.free();
const plugin = await this.ctx.plugin(wasmPath, { wasi: true });
this.plugins.set(name, plugin);
}
async call(name: string, fn: string, input: string): Promise<string> {
const plugin = this.plugins.get(name);
if (!plugin) throw new ();
output = plugin.(fn, input);
output.();
}
(: ): {
..(name)?.();
..(name);
}
}
Anti-Fake-Pass Checklist
❌ Not calling plugin.free() → WASM heap and host context leak
❌ Exposing unrestricted http_request host function → plugins can reach internal APIs
❌ No memory limit → malicious plugin allocates until host OOM
❌ Sharing Plugin instance across concurrent calls → Extism plugins are not thread-safe; one per goroutine/thread
❌ Passing secrets via env vars → plugins can read env if WASI is enabled; pass only via host functions
❌ Hot-reload without freeing old plugin → old WASM instance stays in memory