Space Plugins
Extend your workspace with custom JavaScript plugins that run during meetings, process live transcripts, and render custom file types.
Space Plugins let you extend the Stoa workspace with custom JavaScript or TypeScript. Plugins are regular project files that live in a _stoa_plugins/ directory, sync via CRDT to all participants, and can be co-edited in real time during meetings.
What plugins can do
- Show custom widgets in the sidebar and full panel view
- Render custom file types (e.g., display
.csvfiles as interactive tables) - Process live transcript segments from your meeting in real time
- Fetch external data from APIs and display it in the workspace
Plugins run in sandboxed Web Workers on each participant's browser. There's no deploy step. Edit the file and the output updates automatically for everyone.
Creating a plugin
From the template picker
- Click "Create Plugin" in the Space section of the sidebar.
- Choose a template:
- Widget: sidebar card with a full-panel expanded view
- Renderer: custom display for specific file types
- Transcript: processes live meeting audio
- API Dashboard: fetches and displays external data
- Blank: empty scaffold
- Name your file (must end in
.jsor.ts). - Click Create. The file opens in the editor, synced to all participants.
Manually
Create any .js or .ts file inside a _stoa_plugins/ directory in your project. The plugin system automatically discovers it.
Plugin structure
Every plugin needs a manifest header and a render() function:
// @stoa-plugin
// @name Sprint Board
// @type widget
// @icon BarChart3
module.exports = {
render(context) {
return {
type: 'list',
data: {
items: [
{ title: 'Design review', subtitle: 'In progress' },
{ title: 'API refactor', subtitle: 'Done' }
]
}
}
}
}Manifest fields
| Field | Required | Description |
|---|---|---|
@stoa-plugin | Yes | Marks the file as a plugin |
@name | Yes | Display name in the sidebar |
@type | Yes | widget, renderer, or event |
@icon | No | Lucide icon name (e.g., BarChart3) |
@permissions | No | Network hosts the plugin can access (e.g., network:api.github.com) |
@refresh | No | Auto-refresh interval (e.g., 30s, 5m, 2h) |
@filetype | No | Glob patterns for renderer plugins (e.g., *.csv,*.tsv) |
@events | No | Event subscriptions (e.g., transcript:segment) |
@runtime | No | server to force server-side execution (default is client-side) |
Plugin types
Widget
Appears in the sidebar with a compact view and an expandable full-panel view. Click "Open" on a widget to expand it.
Widget plugins receive context.mode as either 'widget' (sidebar) or 'full' (expanded panel), so you can return different layouts for each.
Renderer
Overrides how specific file types display in the editor. For example, a renderer with @filetype *.csv will display CSV files as formatted tables instead of raw text.
A banner appears above rendered files showing the plugin name, with a toggle to switch between the plugin view and the raw source.
Event (transcript)
Receives live transcript segments during a meeting. Declare @events transcript:segment in the manifest and access context.transcriptSegments in your render function:
// @stoa-plugin
// @name Meeting Tracker
// @type event
// @events transcript:segment
module.exports = {
render(context) {
const segments = context.transcriptSegments || []
const speakers = [...new Set(segments.map(s => s.displayName))]
return {
type: 'list',
data: {
items: speakers.map(name => ({
title: name,
subtitle: `${segments.filter(s => s.displayName === name).length} segments`
}))
}
}
}
}Each transcript segment includes text, speaker, displayName, isFinal, and timestamp.
Output types
Plugins return structured output that renders as native UI components. Supported types:
| Type | Description |
|---|---|
markdown | Formatted markdown with headings, lists, links, and code |
text | Monospace preformatted text |
list | Items with optional icons and subtitles |
kv | Key-value pairs |
table | Columns and rows |
json | Collapsible tree viewer |
tree | Hierarchical expandable nodes |
stack | Vertical or horizontal composition of other outputs |
tabs | Tabbed content switcher |
html | Sandboxed iframe for custom HTML |
error | Error message with optional details |
loading | Spinner with a message |
empty | Empty state with a message |
Network access
Plugins can fetch data from external APIs using standard fetch():
// @stoa-plugin
// @name GitHub Issues
// @type widget
// @permissions network:api.github.com
module.exports = {
async render(context) {
const res = await fetch('https://api.github.com/repos/myorg/myrepo/issues')
const issues = await res.json()
return {
type: 'list',
data: { items: issues.map(i => ({ title: i.title, subtitle: `#${i.number}` })) }
}
}
}For APIs that don't support CORS, use the built-in stoaProxy(url) helper, which routes the request through a server-side proxy with SSRF protection.
Declare which hosts your plugin needs access to in the @permissions field. Use network:* to allow all hosts.
Execution
Plugins run client-side in Web Workers by default. This means near-instant execution with no network round trip.
If you need server-side execution (e.g., for APIs that require server-only secrets), add @runtime server to your manifest. Server-side plugins run in a V8 isolate with a 5-second timeout and 64MB memory limit.
Plugins re-execute automatically when their source changes. Results are cached by source hash, so identical source returns cached output instantly.
Guest access
Guests in a session can view and interact with plugins. Both the plugin execution and proxy endpoints accept guest tokens, so plugins work the same way for guests as they do for team members.