Writing a plugin
This page walks you through scaffolding a plugin, running it locally against a live SLAW instance, and iterating with hot reload.
- SLAW running locally on
http://127.0.0.1:3100(see Quickstart). - Node.js 22 or later, and
pnpm.
1. Scaffold the plugin
Run plugin init outside the SLAW repository:
npx slaw plugin init @acme/hello-plugin --output ~/dev/slaw-plugins
This creates ~/dev/slaw-plugins/hello-plugin/ with:
src/
manifest.ts # plugin id, capabilities, managed resources
worker.ts # definePlugin entry point
ui/index.tsx # UI slot components (if you add UI)
tests/
plugin.spec.ts # Vitest integration test using @slaw/plugin-sdk/testing
esbuild.config.mjs
rollup.config.mjs
Useful flags:
| Flag | Purpose |
|---|---|
--template connector | Starts with event subscription and HTTP outbound wired up |
--template workspace | Starts with local-folder access and scoped API routes |
--category connector,automation | Sets manifest categories |
--display-name "Hello Plugin" | Sets manifest displayName |
--sdk-path <abs-path> | Point at a specific packages/plugins/sdk if you have multiple checkouts |
When plugin init finishes, it prints the next commands verbatim.
2. Install dependencies and start the watch build
cd ~/dev/slaw-plugins/hello-plugin
pnpm install
pnpm dev
pnpm dev runs esbuild --watch and writes dist/manifest.js, dist/worker.js, and dist/ui/ on every save. Leave it running in the background.
3. Install the plugin into SLAW
In a second terminal:
npx slaw plugin install ~/dev/slaw-plugins/hello-plugin
SLAW detects the local path, reads the built manifest, starts the worker process, and begins watching dist/ for changes. You should see output like:
✓ Installed acme.hello-plugin v0.1.0 (ready)
4. Verify the plugin loaded
npx slaw plugin list
npx slaw plugin inspect acme.hello-plugin
list shows key, status, version, and a short error if one exists. inspect shows the full record including lastError.
5. Iterate
Save any .ts file in src/ — esbuild rewrites dist/worker.js — SLAW debounces about 500ms and restarts the worker process. The next call or event fires against the new code.
For UI work, run a second dev server with hot module replacement:
pnpm dev:ui # serves dist/ui/ on http://127.0.0.1:4177
Set devUiUrl: "http://127.0.0.1:4177" in your manifest during development. Remove it before publishing.
Worker entry point
The minimal worker entry uses definePlugin and runWorker from @slaw/plugin-sdk:
import { definePlugin, runWorker } from "@slaw/plugin-sdk";
const plugin = definePlugin({
async setup(ctx) {
// Subscribe to domain events
ctx.events.on("issue.created", async (event) => {
ctx.logger.info("Issue created", { issueId: event.entityId });
});
// Register an agent tool
ctx.tools.register("lookup", {
displayName: "External lookup",
description: "Look up an item in the external system",
parametersSchema: {
type: "object",
properties: { id: { type: "string" } },
required: ["id"],
},
}, async (params) => {
const { id } = params as { id: string };
return { content: `Found ${id}`, data: { id } };
});
// Register a recurring job handler (declare the job in the manifest too)
ctx.jobs.register("daily-sync", async (job) => {
ctx.logger.info("Running daily sync", { scheduledAt: job.scheduledAt });
});
},
async onHealth() {
return { status: "ok", message: "Plugin ready" };
},
});
export default plugin;
runWorker(plugin, import.meta.url);
runWorker(plugin, import.meta.url) starts the JSON-RPC host when the file runs as the main module. When the file is imported (e.g. for tests), the main-module check prevents the host from starting.
Declare capabilities in the manifest
Every host API requires the matching capability in the manifest. A plugin that calls ctx.issues.create() without declaring issues.create is rejected at runtime.
// src/manifest.ts
const manifest: SlawPluginManifestV1 = {
id: "acme.hello-plugin",
apiVersion: 1,
version: "0.1.0",
displayName: "Hello Plugin",
description: "Minimal example plugin.",
author: "Acme",
categories: ["automation"],
capabilities: [
"events.subscribe",
"issues.create",
"jobs.schedule",
"activity.log.write",
],
entrypoints: {
worker: "./dist/worker.js",
},
jobs: [
{
jobKey: "daily-sync",
displayName: "Daily sync",
schedule: "0 9 * * *", // 09:00 UTC daily
},
],
};
Adding managed resources
If your plugin should provision a board-visible agent and a routine that creates issues, declare them in the manifest and call reconcile() in a worker handler:
// manifest: add to capabilities, agents, routines
capabilities: ["agents.managed", "routines.managed"],
agents: [
{
agentKey: "sync-agent",
displayName: "Sync Agent",
adapterType: "claude_local",
status: "paused",
budgetMonthlyCents: 0,
capabilities: "Monitors the external system and files exceptions as SLAW issues.",
},
],
routines: [
{
routineKey: "daily-sync",
title: "Daily sync",
assigneeRef: { resourceKind: "agent", resourceKey: "sync-agent" },
triggers: [{ kind: "schedule", cronExpression: "0 9 * * *", enabled: false }],
},
],
// worker: reconcile in an action handler or on startup
ctx.actions.register("reconcile", async ({ squadId }) => {
const agent = await ctx.agents.managed.reconcile("sync-agent", squadId);
const routine = await ctx.routines.managed.reconcile("daily-sync", squadId);
return { agentId: agent.id, routineId: routine.id };
});
Managed resources start paused so the operator explicitly opts in. See Plugins overview for the full managed resources pattern.
Run the tests
The scaffold generates a test using @slaw/plugin-sdk/testing, which starts an in-memory host without a running SLAW server:
pnpm test
Use createTestHarness() to simulate events, call worker actions, and assert on tool registrations without a real host.
Publish
When you are done iterating locally, publish the package to npm and reinstall the published form:
npm publish --access public
npx slaw plugin install @acme/hello-plugin
Published plugins install from your configured npm registry, version-pin, and produce an install record other operators can reproduce.
Troubleshooting
| Symptom | Likely cause |
|---|---|
Plugin install returned no plugin record | Plugin has not built yet — run pnpm dev or pnpm build first |
| Edits do not reload | Confirm pnpm dev is still running and writing to dist/ |
| Worker restarts but UI is stale | Hard-reload the page, or run pnpm dev:ui for HMR |
Missing capability error at runtime | Add the capability to the manifest and reinstall |
Next steps
- Plugins overview — capability model, managed resources, and plugin categories
- Jira Sync plugin — a complete connector plugin to read alongside your own