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. Features get, set, delete, and list-keys.
  • http: Outbound HTTP requests. Features a fetch method.
  • 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:

  1. Target the wasm32-wasip2 architecture.
    rustup target add wasm32-wasip2
    cargo build --target wasm32-wasip2 --release
    
  2. 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:

  1. Compile the TypeScript output to standard JavaScript.
    npx tsc
    
  2. Componentize the JavaScript using the WIT interface definitions:
    npx componentize-js dist/index.js \
        -w ../ghost-plugin-sdk/wit/ghost-plugin.wit \
        -n ghost-plugin-world \
        --disable http -o plugin.wasm
    
    Note: We disable the built-in node HTTP in favor of the host’s http.fetch via 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!