doc/plugins/PLUGIN_AUTHORING_GUIDE.md
This guide describes the current, implemented way to create a Paperclip plugin in this repo.
It is intentionally narrower than PLUGIN_SPEC.md. The spec includes future ideas; this guide only covers the alpha surface that exists now.
/api/plugins/:pluginId/api/*.ctx.assets is not supported in the current runtime.Use the scaffold package:
pnpm --filter @paperclipai/create-paperclip-plugin build
node packages/plugins/create-paperclip-plugin/dist/index.js @yourscope/plugin-name --output ./packages/plugins/examples
For a plugin that lives outside the Paperclip repo:
pnpm --filter @paperclipai/create-paperclip-plugin build
node packages/plugins/create-paperclip-plugin/dist/index.js @yourscope/plugin-name \
--output /absolute/path/to/plugin-repos \
--sdk-path /absolute/path/to/paperclip/packages/plugins/sdk
That creates a package with:
src/manifest.tssrc/worker.tssrc/ui/index.tsxtests/plugin.spec.tsesbuild.config.mjsrollup.config.mjsInside this monorepo, the scaffold uses workspace:* for @paperclipai/plugin-sdk.
Outside this monorepo, the scaffold snapshots @paperclipai/plugin-sdk from the local Paperclip checkout into a .paperclip-sdk/ tarball so you can build and test a plugin without publishing anything to npm first.
From the generated plugin folder:
pnpm install
pnpm typecheck
pnpm test
pnpm build
For local development, install it into Paperclip from an absolute local path through the plugin manager or API. The server supports local filesystem installs and watches local-path plugins for file changes so worker restarts happen automatically after rebuilds.
Example:
curl -X POST http://127.0.0.1:3100/api/plugins/install \
-H "Content-Type: application/json" \
-d '{"packageName":"/absolute/path/to/your-plugin","isLocalPath":true}'
Worker:
ctx.dbapiRoutesplugin:<pluginKey> origins, blocker relations, checkout assertions, assignment wakeups, and orchestration summariesFirst-party or otherwise trusted orchestration plugins can declare:
database: {
migrationsDir: "migrations",
coreReadTables: ["issues"],
}
Required capabilities are database.namespace.migrate and
database.namespace.read; add database.namespace.write for runtime mutations.
The host derives ctx.db.namespace, runs SQL files in filename order before the
worker starts, records checksums in plugin_migrations, and rejects changed
already-applied migrations.
Migration SQL may create or alter objects only inside ctx.db.namespace. It may
reference whitelisted public core tables for foreign keys or read-only views,
but may not mutate/alter/drop/truncate public tables, create extensions,
triggers, untrusted languages, or runtime multi-statement SQL. Runtime
ctx.db.query() is restricted to SELECT; runtime ctx.db.execute() is
restricted to namespace-local INSERT, UPDATE, and DELETE.
Plugins can expose JSON-only routes under their own namespace:
apiRoutes: [
{
routeKey: "initialize",
method: "POST",
path: "/issues/:issueId/smoke",
auth: "board-or-agent",
capability: "api.routes.register",
checkoutPolicy: "required-for-agent-in-progress",
companyResolution: { from: "issue", param: "issueId" },
},
]
The host resolves the plugin, checks that it is ready, enforces
api.routes.register, matches the declared method/path, resolves company access,
and applies checkout policy before dispatching to the worker's onApiRequest
handler. The worker receives sanitized headers, route params, query, parsed JSON
body, actor context, and company id. Do not use plugin routes to claim core
paths; they always remain under /api/plugins/:pluginId/api/*.
UI:
usePluginDatausePluginActionusePluginStreamusePluginToastuseHostContext@paperclipai/plugin-sdk/uiMount surfaces currently wired in the host include:
pagesettingsPagedashboardWidgetsidebarsidebarPaneldetailTabtaskDetailViewprojectSidebarItemglobalToolbarButtontoolbarButtoncontextMenuItemcommentAnnotationcommentContextMenuItemPlugins may declare a page slot with routePath to own a company route like:
/:companyPrefix/<routePath>
Rules:
routePath must be a single lowercase slugAt minimum:
pnpm --filter <your-plugin-package> typecheck
pnpm --filter <your-plugin-package> test
pnpm --filter <your-plugin-package> build
If you changed host integration too, also run:
pnpm -r typecheck
pnpm test:run
pnpm build