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?
| Kind | Desktop | Mobile | Cloud |
|---|---|---|---|
| Official universal | Component (Cranelift) | Same Component (Pulley) | Official Worker at our deploy. Panels all surfaces. |
| Official desktop-only | Component + network:local + environments: ["desktop"] | — | — |
| Community plugin | Component | Same Component | Panel iframe and/or author backend |
| Any data kind | Files in the project | Files in the project | Files in the project. No Worker. |
Official proofs (now)
These three are the current Rust proofs.
| Plugin | Guest | Panel | What to steal |
|---|---|---|---|
| entity-notes | on-entity-validate via host-entities (duplicate name warning) | Sidebar notes list | Panel + plugin-data REST + a real WIT hook |
| entity-snapshots | Archive on save into plugin storage | Sidebar history | Write-path hook + browse/download |
| content-stats | Read-only project stats | Stats UI | Read-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.
| Sample | Intended lesson |
|---|---|
| hello-world | Smallest panel + Component smoke |
| webhook-automations | Mediated HTTP, declared domains |
| pdf-exporter | Export pipeline + asset write |
| comparison | Multi-entity read UI |
| kanban-board | Standalone page + plugin-data board |
| google-sheets-sync | OAuth settings + HTTP |
| assembly-composer | Assembly-scoped UI + writes |
| blender-bridge | Official 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.