Creating a Plugin

This guide walks through the complete process of building a StudioBrain plugin, from project structure to testing. For a quicker introduction, see Getting Started.

Plugin Structure

Every plugin has the same basic structure:

my-plugin/
  plugin.json              # Plugin manifest (required)
  plugin.wasm              # WASM module (for WASM plugins)
  panels/                  # Frontend panel HTML files
    main-panel.html
  widgets/                 # Field widget HTML files (optional)
    my-widget.html
  assets/                  # Icons, screenshots (optional)
    icon.png
  README.md                # Plugin documentation (optional)

For Python plugins (Desktop/Core only), the structure differs:

my-plugin/
  plugin.json              # Plugin manifest (required)
  backend/
    routes.py              # FastAPI route handlers
    events.py              # Event handler registrations
  panels/
    main-panel.html

plugin.json Schema

The manifest file declares everything about your plugin. Here is the complete schema with all fields:

{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "description": "A short description of what the plugin does",
  "author": "Your Name or Organization",
  "license": "MIT",
  "homepage": "https://github.com/you/my-plugin",
  "repository": "https://github.com/you/my-plugin",
  "icon": "Puzzle",
  "platforms": ["cloud", "desktop", "core"],
  "min_studiobrain_version": "2026.4",
 
  "wasm": {
    "module": "plugin.wasm"
  },
 
  "capabilities": {
    "host_functions": [
      "entity_read",
      "entity_write",
      "asset_read",
      "asset_write",
      "http_request",
      "ai_generate",
      "ui_notify",
      "get_config",
      "set_config"
    ],
    "http_domains": [
      "api.example.com",
      "*.example.org"
    ],
    "frontend": {
      "panels": [
        {
          "id": "main-panel",
          "title": "My Panel",
          "location": "entity-sidebar",
          "url": "/panels/main-panel.html",
          "entity_types": ["character", "location"],
          "default_collapsed": false
        }
      ],
      "tabs": [
        {
          "id": "analytics",
          "title": "Analytics",
          "url": "/panels/analytics.html",
          "entity_types": ["*"]
        }
      ],
      "pages": [
        {
          "id": "dashboard",
          "title": "Plugin Dashboard",
          "url": "/panels/dashboard.html",
          "nav_section": "tools"
        }
      ],
      "field_widgets": [
        {
          "id": "my-widget",
          "label": "My Widget",
          "category": "text",
          "value_type": "string",
          "accepts_options": false
        }
      ]
    },
    "settings": {
      "global": [
        {
          "key": "api_key",
          "label": "API Key",
          "type": "password",
          "required": true,
          "description": "Your external service API key"
        }
      ],
      "user": [
        {
          "key": "show_notifications",
          "label": "Show Notifications",
          "type": "boolean",
          "default": true
        }
      ]
    }
  }
}

Required Fields

FieldTypeDescription
idstringUnique plugin identifier. Lowercase, hyphens only. Must be globally unique for marketplace submission.
namestringHuman-readable display name.
versionstringSemantic version (e.g., 1.0.0).
descriptionstringOne-line description shown in the marketplace.

Optional Fields

FieldTypeDescription
authorstringPlugin author name or organization.
licensestringSPDX license identifier (e.g., MIT, Apache-2.0).
homepagestringURL to the plugin’s documentation or website.
repositorystringURL to the plugin’s source code repository.
iconstringLucide icon name (e.g., Puzzle, Zap, Globe).
platformsstring[]Supported platforms: cloud, desktop, core. Defaults to all.
min_studiobrain_versionstringMinimum StudioBrain version required.

WASM Configuration

FieldTypeDescription
wasm.modulestringPath to the .wasm binary, relative to the plugin directory.

Capabilities

The capabilities object declares what the plugin needs. See Permissions Reference for details on each capability.

host_functions

Array of host function names the plugin will call. Undeclared functions return an error at runtime.

http_domains

Array of domain patterns the plugin will contact via http_request. Supports wildcards (*.example.com). On Cloud, only these domains are reachable. On Desktop/Core, the list is informational.

frontend

Declares UI components the plugin provides:

  • panels — Sidebar panels, footer panels
  • tabs — Entity page tabs
  • pages — Standalone pages
  • field_widgets — Custom form inputs

settings

Declares configuration options:

  • global — Admin-level settings (API keys, service URLs)
  • user — Per-user preferences (notification toggles, display modes)

Setting types: text, password, number, boolean, select, textarea.

Declaring Capabilities

Capabilities are the core of the plugin security model. Declare only what your plugin actually needs.

Request the minimum set of capabilities. Users are more likely to install a plugin that asks for entity_read only than one that asks for entity_write + http_request + file_write.

{
  "capabilities": {
    "host_functions": ["entity_read", "asset_read"]
  }
}
{
  "capabilities": {
    "host_functions": ["entity_read", "entity_write", "ui_notify"]
  }
}
{
  "capabilities": {
    "host_functions": ["entity_read", "http_request"],
    "http_domains": ["api.openai.com", "api.anthropic.com"]
  }
}

Plugin that uses AI generation (auto-granted, BrainBits billing applies)

{
  "capabilities": {
    "host_functions": ["entity_read", "ai_generate", "entity_write"]
  }
}

Declaring Platform Compatibility

The platforms field controls where your plugin appears in the marketplace:

{
  "platforms": ["cloud", "desktop", "core"]
}
ValueEditionRuntime requirement
cloudStudioBrain Cloud (SaaS)Must be WASM. file_read/file_write are blocked.
desktopTauri desktop appWASM or Python/Lua. All capabilities available with consent.
coreSelf-hosted serverWASM or Python/Lua. All capabilities available with consent.

If you omit platforms, the plugin defaults to all three. If your plugin uses file_read or file_write, exclude cloud:

{
  "platforms": ["desktop", "core"],
  "capabilities": {
    "host_functions": ["entity_read", "file_read"]
  }
}

Available Host Functions

WASM plugins interact with StudioBrain through host functions. These are APIs injected by the host runtime into the WASM sandbox. See Host Functions API Reference for complete documentation.

Summary of available functions:

FunctionPurposeCapability
entity_readRead a single entityentity_read
entity_listList entities with filtersentity_read
entity_createCreate a new entityentity_write
entity_updateUpdate an existing entityentity_write
entity_deleteDelete an entityentity_write
asset_readRead an entity’s asset fileasset_read
asset_writeUpload or replace an assetasset_write
http_requestMake an outbound HTTP requesthttp_request
ai_generateCall the AI generation serviceai_generate
ui_notifyShow a toast notification(always available)
get_configRead a plugin setting(always available)
set_configWrite a plugin setting(always available)
storage_getRead plugin-scoped data(always available)
storage_setWrite plugin-scoped data(always available)
storage_deleteDelete plugin-scoped data(always available)
storage_listList keys in plugin storage(always available)
logWrite to the plugin log(always available)

Frontend Capabilities

The most common extension point. Panels render in a sandboxed iframe in the entity editor sidebar.

{
  "frontend": {
    "panels": [
      {
        "id": "my-panel",
        "title": "My Panel",
        "location": "entity-sidebar",
        "url": "/panels/my-panel.html",
        "entity_types": ["character", "location"],
        "default_collapsed": false
      }
    ]
  }
}
  • entity_types — Array of entity types this panel appears on. Use ["*"] for all types.
  • default_collapsed — Whether the panel starts collapsed.

Entity Tabs

Full-width content tabs on entity pages.

{
  "frontend": {
    "tabs": [
      {
        "id": "analytics",
        "title": "Analytics",
        "url": "/panels/analytics.html",
        "entity_types": ["*"]
      }
    ]
  }
}

Standalone Pages

Pages accessible from navigation, independent of any entity.

{
  "frontend": {
    "pages": [
      {
        "id": "dashboard",
        "title": "Plugin Dashboard",
        "url": "/panels/dashboard.html",
        "nav_section": "tools"
      }
    ]
  }
}
  • nav_section — Where the page link appears: tools, settings, or sidebar.

Field Widgets

Custom form inputs that render inline in entity editors. See Building Field Widgets for the complete tutorial.

Building a WASM Plugin

Rust

The recommended language for WASM plugins. Use the StudioBrain Plugin SDK:

# Scaffold the project
npx @biloxistudios/create-plugin my-plugin --template rust
cd my-plugin
 
# Edit src/lib.rs with your plugin logic
 
# Build
cargo build --target wasm32-wasi --release
 
# The output is at target/wasm32-wasi/release/my_plugin.wasm
# Copy or symlink it to plugin.wasm in the project root
cp target/wasm32-wasi/release/my_plugin.wasm plugin.wasm

Minimal Rust plugin:

use studiobrain_plugin_sdk::*;
 
#[export]
fn on_entity_created(entity: Entity) -> Result<(), PluginError> {
    let name = entity.field("name").unwrap_or_default();
    host::log("info", &format!("Entity created: {}", name));
    host::ui_notify("New Entity", &format!("{} was created", name), "info");
    Ok(())
}

AssemblyScript (TypeScript-like)

For developers more comfortable with TypeScript syntax:

npx @biloxistudios/create-plugin my-plugin --template assemblyscript
cd my-plugin
 
# Edit assembly/index.ts
 
# Build
npm run build
# Output: build/plugin.wasm

Minimal AssemblyScript plugin:

import { Entity, host } from "@biloxistudios/plugin-sdk-as";
 
export function on_entity_created(entity: Entity): void {
  const name = entity.field("name");
  host.log("info", `Entity created: ${name}`);
  host.ui_notify("New Entity", `${name} was created`, "info");
}

Python (via componentize-py)

Compile Python to WASM for cross-platform distribution:

npx @biloxistudios/create-plugin my-plugin --template python
cd my-plugin
 
# Edit src/my_plugin.py
 
# Build
componentize-py -d plugin.wit -w plugin componentize my_plugin -o plugin.wasm

Python-to-WASM plugins are distinct from native Python plugins. The compiled WASM binary runs in the sandbox on all platforms, including Cloud. Native Python plugins (in backend/routes.py) run only on Desktop and Core.

Building a Python Plugin (Desktop/Core Only)

Native Python plugins use FastAPI route handlers and event bus registrations. They run as part of the backend process.

my-plugin/
  plugin.json
  backend/
    routes.py
    events.py
  panels/
    main-panel.html

backend/routes.py

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from database import get_db
 
router = APIRouter()
 
@router.get("/status")
async def get_status(db: Session = Depends(get_db)):
    return {"status": "ok", "plugin": "my-plugin"}
 
@router.post("/process")
async def process_entity(request: dict, db: Session = Depends(get_db)):
    entity_type = request.get("entity_type", "character")
    # Plugin logic here
    return {"processed": True}

backend/events.py

from services.event_bus import EventBus, EntityEvent
 
def register(event_bus: EventBus):
    @event_bus.on("entity_created")
    async def on_entity_created(event: EntityEvent):
        print(f"Entity created: {event.entity_type}/{event.entity_id}")

Testing Locally

Install locally

Copy the plugin directory to _Plugins/ in your StudioBrain project:

# Copy plugin to project
cp -r my-plugin/ ~/StudioBrain/MyProject/_Plugins/my-plugin/
 
# Or symlink during development
ln -s $(pwd)/my-plugin ~/StudioBrain/MyProject/_Plugins/my-plugin

Test in development mode

# Web (development server)
cd /path/to/studiobrain-core
npm run dev
# Navigate to Settings > Plugins > Install Local
 
# Desktop (Tauri dev mode)
cd /path/to/studiobrain-app/packages/desktop
cargo tauri dev
# Plugin loads via wasmtime

Verify capabilities

Open the browser console (F12) and check for capability errors. If the plugin tries to call a host function it did not declare, you will see:

[plugin:my-plugin] Error: Host function 'entity_write' not declared in capabilities

Add the missing function to capabilities.host_functions in plugin.json.

Clear the plugin’s capability grants in Settings > Plugins > [Plugin] > Permissions > Reset, then reload the page. The consent dialog should appear with the correct capabilities listed.

Next Steps