Plugin Quick Start
Build a working plugin in 15 minutes.
Step 1: Create the Backend Plugin
Create a Python class with a meta property, a register method, and at least one hook handler:
class HelloPlugin:
def __init__(self, config=None, **kwargs):
self.meta = {
"name": "hello_plugin",
"version": "1.0.0",
"description": "A hello-world plugin",
"priority": 50,
}
async def register(self, bus):
"""Subscribe to lifecycle hooks."""
bus.on("onInputReceived", self.on_input, priority=10)
async def on_input(self, agent, ctx, payload):
"""Handle the onInputReceived event."""
raw_input = payload["raw"]
return {"type": "continue", "patch": {"raw": f"[Hello] {raw_input}"}}
def capabilities(self):
"""Return plugin capabilities."""
return {"greeting": True}
Step 2: Enable in Configuration
Add the plugin to codumentor.yaml:
plugins:
- module: "my_plugins.hello"
class: "HelloPlugin"
args:
greeting: "Hello, world!"
Step 3: Create the UI Plugin
- Copy the template:
``bash``
cp -r ui/plugins/_template ui/plugins/hello_plugin
- Update
manifest.json:
``json``
{
"plugin_id": "hello_plugin",
"name": "Hello Plugin",
"version": "1.0.0",
"description": "A hello-world plugin for learning",
"event_types": ["plugin.hello_plugin.greeted"],
"ui": {
"bundle": "index.js",
"sdk_version": "1.0",
"custom_elements": ["x-hello-plugin-card"],
"placements": ["transcript-card"]
}
}
Optional: Register markdown code-block renderers
If your UI bundle wants to render specific fenced code blocks (for example, a custom diagram language), add markdown_renderers in the ui manifest:
{
"ui": {
"bundle": "index.js",
"sdk_version": "1.0",
"custom_elements": ["x-hello-plugin-card"],
"placements": ["transcript-card"],
"markdown_renderers": [
{
"id": "hello_plugin.diagram_renderer",
"languages": ["mydiagram"],
"export_name": "myDiagramRenderer",
"priority": 50,
"override_existing": false
}
]
}
}
Your bundle must export myDiagramRenderer with the markdown renderer contract (id, languages, and a React component). The host validates and registers it after the extension bundle loads.
Streaming-safety guidance:
- The host runtime already reduces churn (memoized markdown rendering and per-block routing), so unchanged old messages should not re-render during streaming updates.
- Renderer components should still be idempotent: avoid re-running expensive work on every render, and key effects only on the minimum inputs (
code,language,isStreaming). - If rendering is expensive, defer while
isStreaming === trueand render once the message is complete. - Keep visual state stable while async rendering completes (prefer updating content in place over unmount/remount) to avoid layout flashing.
- Create your component in
src/index.ts:
```typescript
import {
BasePluginElement,
bindExpandClick,
getBaseStyles,
escapeHtml,
} from "@codumentor/plugin-sdk";
interface HelloData {
message: string;
}
class HelloPluginCard extends BasePluginElement
render(): void {
const { message } = this._data;
this._shadowRoot.innerHTML = ;
<style>${getBaseStyles()}</style>
<div class="card ${this._status}">
<div class="header">
<span class="icon">${this.getStatusIcon()}</span>
<span class="title">Hello Plugin</span>
</div>
<p>${escapeHtml(message)}</p>
</div>
bindExpandClick(this._shadowRoot, ".header", () => this.toggleExpanded());
}
}
if (!customElements.get("x-hello-plugin-card")) {
customElements.define("x-hello-plugin-card", HelloPluginCard);
}
export { HelloPluginCard };
```
Step 4: Build
cd ui && npm run build:plugins
Step 5: Test
Run Codumentor and verify the plugin loads:
python -m codumentor
Check the logs for hello_plugin registration messages. Send a message through the UI and confirm the [Hello] prefix appears in the processed input.
Plugin Interface Reference
| Method / Property | Required | Description |
|---|---|---|
meta | Yes | Dict with name, version, description, priority, optional dependencies (list of plugin names that must register before this one — see Plugin Load Order) |
register(bus) | Yes | Subscribe to lifecycle hooks via bus.on() |
capabilities() | No | Return a dict describing plugin capabilities |
ui_manifest() | No | Return UI extension configuration. See Backend Hooks |
api_router() | No | Return a FastAPI APIRouter for HTTP endpoints. See API Routes |
cleanup() | No | Instance-level teardown — close connections, cancel tasks, release resources. Called once per instance on application shutdown |
shutdown_all() | No | Classmethod. Tear down class-level shared resources (e.g. shared subprocess pools). Called once per plugin class, after all instance cleanup() calls. See Shutdown & Cleanup |
Next Steps
- Backend Hooks — Complete hook reference and return values
- UI Components — TypeScript components, SDK, and widgets
- Best Practices — Error handling, performance, testing