WASM Developer Documentation
RaveCMS WASM Plugin Development Guide
Welcome to the internal documentation for developing WASM Plugins for RaveCMS!
RaveCMS’s plugin architecture leverages WebAssembly (WASM) and the WebAssembly System Interface (WASI) Preview 2 to provide a secure, high-performance, and language-agnostic extension system. You can build plugins in any language that compiles to the WASI Component Model, but we provide first-class examples and tooling for Rust and TypeScript.
🏗 Overview of the Plugin SDK
The ghost-plugin-sdk provides the canonical interface between the Ghost-Rust host server and the WASM plugins.
The WIT Interface
The communication boundary is defined by a WIT (WebAssembly Interface Type) file at ghost-plugin-sdk/wit/ghost-plugin.wit.
📥 Host Imports (Available to your Plugin)
The host provides several core capabilities that plugins can import and use:
kv: A Key-Value store memory cache. Featuresget,set,delete, andlist-keys.http: Outbound HTTP requests. Features afetchmethod.db: Database access to the host’s standard tables (ghost-query) and custom queries (query).settings: Retrieve site-wide configuration variables (get).
📤 Plugin Exports (Provided by your Plugin)
Plugins must export the following functions (even if they do nothing, they must return an empty/default response):
init(config)render-admin-panel(page-id)transform-post-content(html)transform-product-content(html)validate(payload)run-background-job(job-id)headless-api(path, body, ip)
🦀 Developing Plugins in Rust
The ghost-plugin-sdk provides a Rust crate with a GhostPlugin trait and an easy-to-use register_plugin! macro to handle WIT bindings automatically.
Project Setup
Add the SDK as a dependency in your Cargo.toml:
[dependencies]
ghost-plugin-sdk = { path = "../../ghost-plugin-sdk" }
serde = { version = "1.0", features = ["derive"] }
Implementing a Plugin
Here is a simplified example based on the ai-seo-booster example:
use ghost_plugin_sdk::{register_plugin, GhostPlugin};
struct AiSeoBooster;
// This macro generates all WIT bindings and helper modules (kv, db, http, settings)
register_plugin!(AiSeoBooster);
impl GhostPlugin for AiSeoBooster {
fn transform_post_content(html: String) -> Result<String, String> {
// Read from Ghost settings
let prompt_text = crate::ghost::plugin::settings::get("openai_prompt")
.unwrap_or_else(|| "Summarize this post.".to_string());
// Use Key-Value cache for performance
let cache_key = "ai_summary_123";
if let Some(cached) = crate::ghost::plugin::kv::get(cache_key) {
return Ok(format!("<div class='summary'>{}</div>{}", cached, html));
}
// Perform HTTP Requests using the host capabilities
// let (status, headers, body) = http::fetch("POST", "https://api.openai.com/v1/...", ...)?;
Ok(html)
}
// Must implement remaining trait methods...
fn init(_config: String) -> Result<(), String> { Ok(()) }
fn render_admin_panel(_page_id: String) -> Result<String, String> { Ok(String::new()) }
// ...
}
Building Rust Plugins
To compile a Rust plugin into a WASI Preview 2 component:
- Target the
wasm32-wasip2architecture.rustup target add wasm32-wasip2 cargo build --target wasm32-wasip2 --release - Embed the component model using
wasm-tools:wasm-tools component embed ../ghost-plugin-sdk/wit/ghost-plugin.wit target/wasm32-wasip2/release/ai_seo_booster.wasm -o plugin.wasm
📘 Developing Plugins in TypeScript
Building WASM plugins in TypeScript requires compiling to JS, then using componentize-js (developed by the Bytecode Alliance) to wrap the JS in a SpiderMonkey engine instance that conforms to the WASI Component Model.
Project Setup
In your TypeScript repo, you will typically import the generated TypeScript definitions of the SDK:
import { type types, settings, kv, http } from "../../sdk/ghost-plugin";
Note: Ensure your tsconfig.json compiles to typical CommonJS or ES modules compatible with standard NodeJS resolution.
Implementing a Plugin
Here is the TypeScript equivalent for ai-seo-booster:
import { type types, settings, kv, http } from "../../sdk/ghost-plugin";
export const init: types.PluginExports["init"] = (config: string) => {};
export const transformPostContent: types.PluginExports["transformPostContent"] = (html: string) => {
// Read from Ghost Settings
const openaiApiKey = settings.get("openai_api_key");
if (!openaiApiKey) {
return html;
}
// Use Key-Value cache
const cacheKey = `ai_summary_cache`;
const cached = kv.get(cacheKey);
if (cached) {
return `<div class='summary'>${cached}</div>${html}`;
}
try {
// Perform HTTP Outbound Request
const response = http.fetch(
"POST",
"https://api.openai.com/v1/chat/completions",
[
["Authorization", `Bearer ${openaiApiKey}`],
["Content-Type", "application/json"],
],
"{...}" // Stringified payload
);
const [status, headers, body] = response;
// Process response and kv.set() the cache...
} catch(e: any) {
return `<!-- Error: ${e.message} -->${html}`;
}
return html;
};
// Implement remaining exported functions
export const transformProductContent: types.PluginExports["transformProductContent"] = (html: string) => html;
export const validate: types.PluginExports["validate"] = (payload: string) => "";
export const runBackgroundJob: types.PluginExports["runBackgroundJob"] = (jobId: string) => {};
export const headlessApi: types.PluginExports["headlessApi"] = (path: string, body: string, ip: string) => "{}";
export const renderAdminPanel: types.PluginExports["renderAdminPanel"] = (pageId: string) => "";
Building TypeScript Plugins
To compile a TypeScript plugin into a WASI Component:
- Compile the TypeScript output to standard JavaScript.
npx tsc - Componentize the JavaScript using the WIT interface definitions:
Note: We disable the built-in node HTTP in favor of the host’snpx componentize-js dist/index.js \ -w ../ghost-plugin-sdk/wit/ghost-plugin.wit \ -n ghost-plugin-world \ --disable http -o plugin.wasmhttp.fetchvia WIT imports.
🚀 Publishing & Deployment
Ghost-Rust serves .wasm plugins from the ./plugins/<plugin-name>/ directory. A deployment script deploy.sh is provided in the ghost-plugin-examples directory. Or you can simply upload your plugins directly from the admin UI.
You can automatically compile and move the compiled binaries to the correct directory:
# Deploy all Rust examples
./ghost-plugin-examples/deploy.sh rust
# Deploy all TS examples
./ghost-plugin-examples/deploy.sh typescript
This script will run cargo build / npx tsc and package your WASM modules with wasm-tools component embed / componentize-js, migrating the plugin.wasm payload, plugin.toml, and migrations securely into the ghost-server/plugins folder!