RaisFastRaisFast
Plugins

WASM Plugins

Build high-performance plugins in Rust, Go, C, or Zig — compiled to WebAssembly with strong typing via WIT.

WASM plugins run at near-native speed with strong sandboxing. Use any language that compiles to WebAssembly.

Why WASM?

Script Plugins (JS/Lua/Rhai)WASM Plugins
PerformanceGoodNear-native
LanguageJS / Lua / Rhai onlyRust, Go, C, Zig, etc.
TypingDynamicStrong (WIT interface)
ConcurrencyPer-request (no state)Instance pool (stateful)
Best forQuick integrations, hooksHeavy computation, data processing

Quick Start (Rust)

1. Create the project

cargo new --lib my-plugin
cd my-plugin

2. Add dependencies

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

[dependencies]
wit-bindgen = "0.36"

3. Write the plugin

Download the plugin.wit interface file, then:

wit_bindgen::generate!({
    path: "../plugin-wit/plugin.wit",
    world: "plugin-world",
});

use exports::raisfast::plugin_wit::plugin_hooks::*;

struct Plugin;

impl Guest for Plugin {
    fn on_post_creating(input: PostInput) -> Option<PostInput> {
        let mut post = input;
        if post.slug.is_none() {
            post.slug = Some(post.title.to_lowercase().replace(' ', "-"));
        }
        Some(post)
    }

    fn on_post_created(output: PostOutput) {
        host_api::log("info", &format!("published: {}", output.slug));
        let key = format!("cache/posts/{}.json", output.slug);
        host_api::vfs_write(&key, "{}");
    }

    fn on_cron_tick(payload: Option<String>) {
        if let Some(p) = payload {
            host_api::log("info", &format!("cron fired: {p}"));
        }
    }
}

export_plugin_hooks!(Plugin);

4. Build

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

5. Manifest

[plugin]
id = "com.example.my-plugin"
name = "My WASM Plugin"
version = "0.1.0"
runtime = "wasm"
entry = "plugin.wasm"

[hooks.on-post-creating]
priority = 10

[hooks.on-post-created]
priority = 10

[[cron]]
label = "Scheduled Task"
job_type = "my_task"
cron_expr = "0 */6 * * *"

6. Deploy

extensions/plugins/my-plugin/
├── manifest.toml
└── plugin.wasm

Restart the server or let hot reload pick it up.

WIT Interface

WASM plugins implement the plugin-hooks interface and import host-api.

Host API (imports)

Functions your plugin can call:

interface host-api {
    log: func(level: string, msg: string);
    get-config: func(key: string) -> option<string>;
    http-get: func(url: string) -> option<string>;
    http-post: func(url: string, body: string) -> option<string>;
    get-data: func(key: string) -> option<string>;
    set-data: func(key: string, value: string) -> bool;
    get-post: func(slug: string) -> option<string>;
    db-query: func(sql: string, params: option<string>) -> string;
    db-execute: func(sql: string, params: option<string>) -> string;
    db-begin: func() -> string;
    db-commit: func() -> string;
    db-rollback: func() -> string;
    vfs-read: func(path: string) -> option<string>;
    vfs-write: func(path: string, content: string) -> bool;
    vfs-delete: func(path: string) -> bool;
    vfs-exists: func(path: string) -> bool;
    vfs-list: func(path: string) -> option<string>;
    vfs-stat: func(path: string) -> option<string>;
    emit-event: func(event-type: string, data: string) -> string;
}

Plugin Hooks (exports)

Functions your plugin can export:

interface plugin-hooks {
    // Filters — return Some to modify, None to skip
    on-post-creating: func(input: post-input) -> option<post-input>;
    on-post-updating: func(input: post-input) -> option<post-input>;
    on-comment-creating: func(input: comment-input) -> option<comment-input>;
    on-content-creating: func(input: content-event) -> option<content-event>;
    on-content-updating: func(input: content-event) -> option<content-event>;
    render-markdown: func(input: string) -> option<string>;
    filter-html: func(input: string) -> option<string>;

    // Actions — return value ignored
    on-post-created: func(output: post-output);
    on-post-updated: func(output: post-output);
    on-post-deleted: func(id: string);
    on-comment-created: func(input: comment-input);
    on-content-created: func(input: content-event);
    on-content-updated: func(input: content-event);
    on-content-deleted: func(content-type: string, id: string);
    on-login: func(user-id: string);
    on-cron-tick: func(payload: option<string>);
}

WIT Types

record post-input {
    title: string,
    content: string,
    slug: option<string>,
    excerpt: option<string>,
    category-id: option<string>,
    tag-ids: option<list<string>>,
    status: option<string>,
    cover-image: option<string>,
}

record post-output {
    id: string,
    title: string,
    slug: string,
    content: string,
    excerpt: option<string>,
    status: string,
    created-by: string,
    updated-by: option<string>,
    category-id: option<string>,
    view-count: s64,
    created-at: string,
    updated-at: string,
    published-at: option<string>,
}

record comment-input {
    content: string,
    nickname: option<string>,
    email: option<string>,
    parent-id: option<string>,
}

record content-event {
    content-type: string,
    data: string,
    id: option<string>,
}

Concurrency Model

Unlike JS/Lua (per-request, stateless), WASM uses an instance pool:

  • N pre-compiled instances (configurable via PLUGIN_WASM_POOL_SIZE, default 4)
  • Round-robin dispatch for concurrent requests
  • Instances are reused — you can hold state between calls
  • Perfect isolation between instances

Current Limitations

The WIT interface provides only raw SQL (db-query, db-execute). The following functions available in JS/Lua/Rhai are not yet exposed to WASM:

  • High-level DB API: dbInsert, dbFetchOne, dbFetchAll, dbUpdate, dbDelete, dbCount, dbIncrement, dbSum, dbGroupBy
  • newId (UUID v7 generation)
  • dbPh (SQL placeholder)

Use raw SQL with db-query and db-execute as a workaround.

Using Other Languages

Any language that targets wasm32-unknown-unknown and supports the Component Model works:

LanguageBindgen Tool
Rustwit-bindgen
Gowit-bindgen-go
Cwit-bindgen-c
ZigCommunity tooling

The WIT interface file is the single contract — as long as your language can implement it, you're good.

On this page