Skip to main content

Native WebAssembly

A native WebAssembly plugin is a self-contained module that implements the Owncast plugin wire protocol directly. It does not use the JavaScript or Python SDK and it does not run on their shared language engines.

Use this path when you need a compiled language, want to reuse an existing library, or need direct control over the WebAssembly interface. For most plugins, the JavaScript SDK or Python SDK requires less code and tooling.

Choose a language and Extism PDK

Any language that can produce an Extism-compatible WebAssembly module can be used.

LanguageExtism PDKBuild target
Rustextism-pdkwasm32-unknown-unknown
TinyGogo-pdkwasip1 with -buildmode=c-shared
AssemblyScript@extism/as-pdkWebAssembly through asc
Zig and othersExtism PDKsDepends on the language

The examples below implement the smallest valid Owncast plugin. Each exports register, reads the packaged manifest that Owncast injected, and returns it unchanged. They intentionally register no events or commands.

Add extism-pdk to a library crate that builds as cdylib:

Cargo.toml
[package]
name = "my-plugin"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
extism-pdk = "1"
src/lib.rs
use extism_pdk::*;

#[plugin_fn]
pub fn register() -> FnResult<String> {
Ok(config::get("manifest")?
.ok_or_else(|| Error::msg("Owncast did not inject the plugin manifest"))?)
}

Build it:

rustup target add wasm32-unknown-unknown
cargo build --release --target wasm32-unknown-unknown
cp target/wasm32-unknown-unknown/release/my_plugin.wasm my-plugin.wasm

The plugin SDK repository also contains a complete Rust example that receives a chat event and calls an Owncast host function.

Implement the Owncast wire protocol

Every native plugin must export register. Export the other entry points your plugin uses, such as on_event, on_filter, or on_http_request. Declare Owncast host functions as imports from the extism:host/user module. Your language's Extism PDK handles memory shared with those imports.

The Owncast wire protocol is the source of truth for:

  • Plugin exports and their input and output shapes.
  • Owncast host imports and their required permissions.
  • Event envelopes, filter results, and HTTP request responses.
  • Timeouts and size limits.

Native modules use the same manifest, events, host functions, and permission checks as JavaScript and Python plugins. They only replace the language SDK layer.

Read the packaged manifest

Owncast sets reserved Extism config values before calling the plugin:

KeyNative WebAssembly value
manifestThe manifest Owncast loaded, unchanged.
__slugThe canonical slug resolved by Owncast. Use this when the plugin needs its identity.
scriptNot set. The WebAssembly module is already the plugin code.

Read manifest in register and return that JSON. Do not compile a second copy of the manifest into the module. The packaged manifest is what the administrator reviewed and what Owncast loaded, so returning it prevents two copies from drifting apart.

A language SDK derives subscriptions and commands from registered handlers. A native module has no SDK to do that work, so declare any subscriptions and commands entries it needs in plugin.manifest.json. Returning the injected manifest from register reports those declarations to Owncast.

A minimal manifest shared by all three examples is:

plugin.manifest.json
{
"api": "1",
"name": "My Plugin",
"slug": "my-plugin",
"version": "0.1.0"
}

Package the module

For loose installation and local testing, place the compiled module beside its project manifest:

my-plugin.wasm
plugin.manifest.json

For a loose installation in Owncast's data/plugins/ directory, rename the manifest so both files have the same basename:

my-plugin.wasm
my-plugin.manifest.json

For an installable .ocpkg, create a ZIP archive with canonical names:

my-plugin.ocpkg
├── plugin.manifest.json
├── plugin.wasm
├── icon.png optional
├── INSTRUCTIONS.md optional
├── public/ optional, web-served files
└── assets/ optional, host-read files

The module inside the archive must be named plugin.wasm, regardless of the plugin slug. Owncast uses that filename to select the native WebAssembly runtime.

See Packaging and publishing for installation, updates, icons, instructions, and the public plugin directory.

Test before installing

Clone the plugin SDK repository and build owncast-plugin-test once:

git clone https://github.com/owncast/plugin-sdk.git
cd plugin-sdk
./tools/bootstrap.sh

Run it against the directory containing <slug>.wasm, plugin.manifest.json, and any scenarios:

./tools/owncast-plugin-test path/to/my-plugin

Use --load-only when the plugin has no scenario files:

./tools/owncast-plugin-test --load-only path/to/my-plugin

The load check calls register, compares the returned identity and permissions with the manifest, validates permission-carrying subscriptions, and instantiates the same host-function surface with test implementations. Fix any load failure before creating the .ocpkg.


Improve this page

See something missing or incorrect? Edit this page and improve the documentation for everyone.

Contributors to this documentation

Related Documents