Skip to main content

Writing a plugin

This page walks you through scaffolding a plugin, running it locally against a live SLAW instance, and iterating with hot reload.

Prerequisites
  • 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:

FlagPurpose
--template connectorStarts with event subscription and HTTP outbound wired up
--template workspaceStarts with local-folder access and scoped API routes
--category connector,automationSets 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

SymptomLikely cause
Plugin install returned no plugin recordPlugin has not built yet — run pnpm dev or pnpm build first
Edits do not reloadConfirm pnpm dev is still running and writing to dist/
Worker restarts but UI is staleHard-reload the page, or run pnpm dev:ui for HMR
Missing capability error at runtimeAdd the capability to the manifest and reinstall

Next steps