Build plugins that
extend SocialScale.
Write a plugin in Rust, Go, C, or AssemblyScript and compile it to WebAssembly. SocialScale loads it inside a Wasmtime sandbox — safe, isolated, and blazing fast.
What you can build
Five capability types, each targeting a different integration point in the SocialScale pipeline.
PlatformConnector Add support for a new social platform. Implements OAuth, publish, delete, fetch comments, and analytics endpoints.
ContentTransformer Transform post content before publishing — translate, reformat for a platform, add watermarks, or apply brand filters.
PolicyEvaluator Custom compliance and content safety rules that run before every publish. Block, warn, or auto-correct based on your policies.
AnalyticsProcessor Enrich or aggregate analytics data from custom sources, normalise third-party metrics, or compute derived KPIs.
WebhookHandler Handle incoming webhooks from platforms or external services and route them into the SocialScale event bus.
Custom(String) Arbitrary custom capability string for plugins that don't fit the built-in types. Registered with the host registry under your custom identifier.
Getting started
Build, test, and publish your first plugin in 5 steps.
1 Write the manifest
{
"id": "com.example.my-platform",
"name": "My Platform Connector",
"version": "1.0.0",
"description": "Adds My Platform to SocialScale",
"author": "Your Name <you@example.com>",
"capabilities": ["platform_connector"],
"wasm_path": "./target/wasm32-wasi/release/my_plugin.wasm",
"config_schema": {
"type": "object",
"properties": {
"api_key": {
"type": "string",
"description": "My Platform API key"
}
},
"required": ["api_key"]
}
} Your plugin must export: alloc, dealloc, plugin_init, plugin_capabilities, and plugin_handle_request. See the ABI reference below.
cargo build --target wasm32-wasi --release # Output: target/wasm32-wasi/release/my_plugin.wasm
socialscale plugin install ./plugin.json # Loads the .wasm from the path in the manifest
socialscale plugin publish # Uploads to the registry and assigns a semver tag
ABI Reference
ABI version 1. All data crosses the host–plugin boundary as JSON, encoded in WASM linear memory using ptr+len pairs.
// 64-bit encoding: (ptr << 32) | len // // Plugin returns: i64 = encode_ptr_len(ptr, len) // Host decodes: let (ptr, len) = decode_ptr_len(packed); // // All data crossing the boundary is JSON, passed through // WASM linear memory using ptr+len pairs.
↑ Plugin exports (plugin → host)
alloc(size: i32) → i32 Allocate memory in WASM linear memory. Host uses this before writing input data.
dealloc(ptr: i32, size: i32) Free memory previously allocated by alloc.
plugin_init() → i64 Return plugin metadata (id, name, version, abi_version) as ptr+len encoded JSON.
plugin_capabilities() → i64 Declare what the plugin can do (publish_text, read_analytics, platforms…).
plugin_handle_request(ptr: i32, len: i32) → i64 Main entry point. Receives a JSON-encoded PluginRequest, returns a JSON-encoded PluginResponse.
↓ Host functions (provided to plugin)
host_log(level: i32, ptr: i32, len: i32) Write a log message at the given level (0=trace … 4=error).
host_http_request(ptr: i32, len: i32) → i64 Make an outbound HTTP request. Domain must be in the allowlist declared in capabilities.
host_store_get(kptr: i32, klen: i32) → i64 Read a value from the plugin KV store by key. Returns ptr+len of the value.
host_store_set(kp,kl,vp,vl: i32) Write a key-value pair to the plugin KV store.
host_emit_event(ptr: i32, len: i32) Emit a JSON event on the host event bus for other plugins or agents to consume.
Host module name: "env". Import in Rust with #[link(wasm_import_module = "env")].
Capabilities & Permissions
Plugins declare what they need in their manifest. Users approve on install.
The host enforces at runtime — requests to undeclared domains are blocked, KV access requires storage_access, and all actions are audit-logged.
| Capability | Approval | What it grants |
|---|---|---|
network_access | Requires approval | Outbound HTTP requests. Declare the allowed domains in required_network_domains. Host enforces a per-domain allowlist at runtime. |
storage_access | Requires approval | Read and write values in the plugin-scoped KV store. Keys are namespaced to your plugin ID. |
event_emit | Requires approval | Emit events on the host event bus. Other plugins or agents may subscribe. |
credential_access | Explicit approval | Access OAuth tokens for the platform this plugin serves. Requires explicit user approval at install time. |
Rate limiting
Every plugin has a sliding-window rate limit of 1 000 outbound HTTP requests per hour by default. Exceeded requests are rejected with a RateLimitExceeded error. Limits can be configured per-plugin by the administrator.
Request & Response types
All calls to plugin_handle_request carry a tagged union JSON payload with a "type" discriminant.
PluginRequest variants
Publish text, media_urls, hashtags, metadata Publish content to the platform. Core method for all PlatformConnectors.
DeletePost post_id Delete a published post by its platform-native ID.
FetchComments post_id, cursor? Fetch comments on a post. Use cursor for pagination.
ReplyComment comment_id, text Reply to a specific comment.
FetchInsights start (ISO 8601), end (ISO 8601) Fetch analytics for a date range.
FetchPostMetrics post_id Fetch engagement metrics for a single post.
ExchangeCode code, redirect_uri Exchange an OAuth authorization code for tokens.
RefreshToken refresh_token Refresh an expired OAuth access token.
Custom action: string, payload: JSON Arbitrary custom action for plugin-specific behaviour.
Success response
{
"status": "ok",
// any additional fields are
// flattened into the response
"post_id": "abc123",
"url": "https://..."
} Error response
{
"status": "error",
"code": "rate_limit_exceeded",
"message": "Try again in 60s",
"retryable": true
} Minimal platform connector
A complete skeleton showing all 5 required exports. Compile with cargo build --target wasm32-wasi --release.
use serde::{Deserialize, Serialize};
use serde_json::Value;
// Required by the host to allocate/free memory
#[no_mangle]
pub extern "C" fn alloc(size: i32) -> i32 {
let mut buf = Vec::<u8>::with_capacity(size as usize);
let ptr = buf.as_mut_ptr() as i32;
std::mem::forget(buf);
ptr
}
#[no_mangle]
pub extern "C" fn dealloc(ptr: i32, size: i32) {
unsafe { drop(Vec::from_raw_parts(ptr as *mut u8, 0, size as usize)) }
}
// Metadata returned to the host
#[no_mangle]
pub extern "C" fn plugin_init() -> i64 {
let meta = serde_json::json!({
"abi_version": 1,
"id": "com.example.my-platform",
"name": "My Platform Connector",
"version": "1.0.0",
"author": "Your Name"
});
return_json(&meta)
}
// Declare capabilities
#[no_mangle]
pub extern "C" fn plugin_capabilities() -> i64 {
let caps = serde_json::json!({
"abi_version": 1,
"can_publish_text": true,
"can_publish_image": true,
"can_publish_video": false,
"can_read_analytics": true,
"can_manage_ads": false,
"platforms": ["my-platform"],
"required_network_domains": ["api.my-platform.com"],
"needs_kv_store": true
});
return_json(&caps)
}
// Main request handler
#[no_mangle]
pub extern "C" fn plugin_handle_request(ptr: i32, len: i32) -> i64 {
let request: Value = read_json(ptr, len);
let response = match request["type"].as_str().unwrap_or("") {
"publish" => handle_publish(&request),
"fetch_insights" => handle_insights(&request),
"exchange_code" => handle_oauth(&request),
_ => serde_json::json!({
"status": "error",
"code": "unsupported",
"message": "Request type not supported",
"retryable": false
}),
};
return_json(&response)
} [package]
name = "my-plugin"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib"] # ← required for WASM
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# WASM target: cargo build --target wasm32-wasi --release Security model
Every plugin runs in a fully isolated Wasmtime sandbox. No host memory is accessible; all I/O goes through typed host functions.
Plugins have their own linear memory. They cannot read or write host memory directly. All data exchange goes through the alloc/dealloc ABI.
Every outbound HTTP request is checked against the domain allowlist the plugin declared at install. Requests to unlisted domains are rejected immediately.
Plugins can only use the capabilities they declared and the user approved. Attempting to call host_store_get without storage_access returns an error.
Every capability check, network request, store read/write, and event emit is recorded with a timestamp in the audit log. Viewable in the dashboard.
1 000 outbound HTTP requests per hour by default, enforced with a sliding-window algorithm. Exceeding the limit returns a retryable error.
No plugin runs until the user has explicitly approved its capabilities. Capability re-requests after install require a new approval prompt.
Build your first plugin
Install the CLI, scaffold a plugin, and have it running locally in under 10 minutes.
Questions? hello@socialscale.zuhabul.com