Plugin Iframe Protocol Reference
Bidirectional communication between the StudioBrain host application and plugin iframes.
Overview
Plugins can register UI panels that render inside sandboxed iframes within the entity editor. Communication between the host app and plugin iframes uses the browser postMessage API with structured typed messages.
All types are defined in src/lib/plugin-message-protocol.ts.
Theme compliance note: Plugin iframes do not inherit the host’s CSS variables. Plugins must implement their own theme handling using the surface variable pattern. See the Plugin Development page for mandatory theme compliance requirements.
Host to Plugin Messages
Sent by StudioBrain to the plugin iframe.
entity-context
Sent when the iframe loads. Provides the plugin with entity data and environment info.
{
type: 'entity-context';
entityType: string; // e.g., "character"
entityId: string; // e.g., "rex_marshall"
data: Record<string, any>; // Full entity fields + markdown_body
}theme-change
Sent when the user toggles between light and dark mode.
{
type: 'theme-change';
theme: 'light' | 'dark';
}entity-updated
Sent when entity data changes (e.g., after a save or field update).
{
type: 'entity-updated';
data: Record<string, any>;
}Plugin to Host Messages
Sent by the plugin iframe to StudioBrain.
request-entity-data
Request the current entity data. The host responds with an entity-context message.
{
type: 'request-entity-data';
}entity-modified
Request to update entity fields. The host merges these into the current entity data.
{
type: 'entity-modified';
changes: Record<string, any>; // e.g., { description: "Updated text" }
}navigate
Request to navigate the host application to a different route.
{
type: 'navigate';
path: string; // e.g., "/characters/rex_marshall"
}toast
Request to display a toast notification in the host UI.
{
type: 'toast';
level: 'success' | 'error' | 'info';
message: string;
}resize
Request to resize the iframe container height.
{
type: 'resize';
height: number; // pixels
}Security
Origin Validation
- The host validates
event.originbefore processing any incoming plugin messages. - Plugins should validate that messages come from the expected host origin.
- Iframes use the
sandboxattribute:allow-scripts allow-same-origin allow-forms
Type Guards
import { isPluginMessage, isHostMessage } from '@/lib/plugin-message-protocol';
// In host code -- validate incoming plugin messages
window.addEventListener('message', (event) => {
if (isPluginMessage(event.data)) {
// Safe to handle event.data as PluginToHostMessage
}
});
// In plugin code -- validate incoming host messages
window.addEventListener('message', (event) => {
if (isHostMessage(event.data)) {
// Safe to handle event.data as HostToPluginMessage
}
});Plugin Usage Example
Receiving Entity Context
window.addEventListener('message', (event) => {
if (event.data?.type === 'entity-context') {
const { entityType, entityId, data } = event.data;
// Initialize plugin UI with entity data
renderPluginUI(data);
}
if (event.data?.type === 'theme-change') {
applyTheme(event.data.theme);
}
if (event.data?.type === 'entity-updated') {
// Refresh plugin UI with new data
renderPluginUI(event.data.data);
}
});Updating Entity Data
// Request field update
window.parent.postMessage({
type: 'entity-modified',
changes: { description: 'Updated by plugin' }
}, '*');Requesting Fresh Data
// Ask the host for the latest entity data
window.parent.postMessage({
type: 'request-entity-data'
}, '*');Auto-Resize
// Notify host of content height changes
const observer = new ResizeObserver(() => {
window.parent.postMessage({
type: 'resize',
height: document.body.scrollHeight
}, '*');
});
observer.observe(document.body);Navigation and Toast
// Navigate to another entity
window.parent.postMessage({
type: 'navigate',
path: '/characters/rex_marshall'
}, '*');
// Show success toast
window.parent.postMessage({
type: 'toast',
level: 'success',
message: 'Analysis complete!'
}, '*');Plugin Registration
Plugins declare panels in their plugin.json manifest:
{
"name": "my-plugin",
"capabilities": {
"frontend": {
"panels": [
{
"id": "analysis-panel",
"title": "Entity Analysis",
"location": "entity-sidebar",
"entity_types": ["character", "location"],
"url": "/panels/analysis.html"
},
{
"id": "stats-tab",
"title": "Statistics",
"location": "entity-tab",
"url": "/panels/stats.html"
}
]
}
}
}Panel Locations
| Location | Renders As | Description |
|---|---|---|
entity-sidebar | Collapsible section | Shown alongside Visual Editor tab |
entity-tab | Full tab | Auto-injected into EntityEditTabs |
entity-footer | Below editor | Reserved for future use |
Component Block Registration
Plugin panels can also be placed as component blocks in layouts. The component ID format is plugin:pluginId:panelId, e.g., plugin:my-plugin:analysis-panel.
These are rendered by ComponentBlockRenderer which delegates to PluginBlockIframe for any component ID matching the plugin:*:* pattern.
TypeScript Type Definitions
The complete type definitions from src/lib/plugin-message-protocol.ts:
// Host to Plugin
type HostToPluginMessage =
| EntityContextMessage // { type: 'entity-context', entityType, entityId, data }
| ThemeChangeMessage // { type: 'theme-change', theme }
| EntityUpdatedMessage; // { type: 'entity-updated', data }
// Plugin to Host
type PluginToHostMessage =
| RequestEntityDataMessage // { type: 'request-entity-data' }
| EntityModifiedMessage // { type: 'entity-modified', changes }
| NavigateMessage // { type: 'navigate', path }
| ToastMessage // { type: 'toast', level, message }
| ResizeMessage; // { type: 'resize', height }