Plugin Examples

Official proofs live in studiobrain-plugins as Rust WIT Components. Copy those trees. Do not start from legacy Python under studiobrain-templates/plugins/.

Where does it run?

KindDesktopMobileCloud
Official universalComponent (Cranelift)Same Component (Pulley)Official Worker at our deploy. Panels all surfaces.
Official desktop-onlyComponent + network:local + environments: ["desktop"]
Community pluginComponentSame ComponentPanel iframe and/or author backend
Any data kindFiles in the projectFiles in the projectFiles in the project. No Worker.

Official proofs (now)

These three are the current Rust proofs.

PluginGuestPanelWhat to steal
entity-noteson-entity-validate via host-entities (duplicate name warning)Sidebar notes listPanel + plugin-data REST + a real WIT hook
entity-snapshotsArchive on save into plugin storageSidebar historyWrite-path hook + browse/download
content-statsRead-only project statsStats UIRead-only Component, no writes

Each proof is cargo component against the StudioBrain plugin WIT world. Desktop/mobile load plugin.wasm. Cloud official execution is a Worker we compile at deploy — the proof’s panel still runs everywhere as an iframe.

Build and layout notes for entity-notes (the usual starting point): plugin.json, src/lib.rs, frontend/*.html, cargo component build --release, copy the Component to plugin.wasm. See that plugin’s README in studiobrain-plugins.

entity-notes (shape)

{
  "id": "entity-notes",
  "name": "Entity Notes",
  "version": "0.1.0",
  "trust_tier": "first_party",
  "type": "full",
  "capabilities": {
    "backend": { "entry": "plugin.wasm" },
    "frontend": {
      "panels": [
        {
          "id": "notes",
          "label": "Notes",
          "location": "entity-sidebar"
        }
      ]
    }
  },
  "permissions": ["read_entities"]
}

The notes panel talks to /api/plugins/entity-notes/data/note (plugin-data store). The Component hook is independent of the panel: it queries entities through WIT and returns an advisory warning. That split — static panel vs Component hook vs official cloud Worker — is the architecture.

entity-snapshots (shape)

Save hook writes a JSON snapshot into plugin storage. The sidebar lists and downloads history. Permissions are plugin-data read/write, not a license to scrape the host disk.

content-stats (shape)

Read-only aggregation. Use it when you need a guest that never writes entities.

Follow-on samples (tickets)

These are not the current official proofs. Track them as tickets; do not document them as if Marketplace already ships them as first-party proofs.

SampleIntended lesson
hello-worldSmallest panel + Component smoke
webhook-automationsMediated HTTP, declared domains
pdf-exporterExport pipeline + asset write
comparisonMulti-entity read UI
kanban-boardStandalone page + plugin-data board
google-sheets-syncOAuth settings + HTTP
assembly-composerAssembly-scoped UI + writes
blender-bridgeOfficial desktop-only + network:local

A blender-class bridge that talks to a local app is official desktop-only (network:local + environments: ["desktop"]). It must not be listed as a cloud Worker.

prompt-engine is not a plugin. Core owns prompt construction. Do not treat it as a proof or a community listing.

Panel-only pattern (any surface)

Cloud community plugins that have no author backend are this: HTML + postMessage, no expectation that plugin.wasm runs in our isolate.

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      font-family: system-ui, sans-serif;
      background: var(--surface-base-bg, #fff);
      color: var(--surface-base-text, #1a1a2e);
      padding: 16px;
      margin: 0;
    }
  </style>
</head>
<body>
  <div id="greeting">Loading...</div>
  <script>
    window.addEventListener('message', (event) => {
      if (event.data?.type === 'entity-context') {
        const { entityType, entityId, data } = event.data;
        document.getElementById('greeting').textContent =
          (data && data.name) || entityId;
        document.documentElement.classList.toggle('dark', event.data.theme === 'dark');
      }
      if (event.data?.type === 'theme-change') {
        document.documentElement.classList.toggle('dark', event.data.theme === 'dark');
      }
    });
    new ResizeObserver(() => {
      window.parent.postMessage({ type: 'resize', height: document.body.scrollHeight }, '*');
    }).observe(document.body);
  </script>
</body>
</html>

Theme rules: Plugin Development. Full messages: Plugin Iframe Protocol.

Settings and plugin-data

Proofs and samples that persist state use the plugin-data API and settings in the manifest — not a sidecar database. Host function names and REST paths are in Getting Started and Host Functions.

See also