Paperclip Plugin Architecture: How to Build Custom Plugins for Paperclip

Paperclip's extensibility is powered by a plugin SDK that uses a three-part architecture: a manifest declaring capabilities and UI slots, a worker process communicating over JSON-RPC, and an optional React UI bundle that renders inside the host application.

Paperclip is an open-source AI-powered workspace platform. Its modular design allows developers to extend core functionality without modifying the main codebase. This guide explains how the Paperclip plugin architecture works and how you can build your own custom plugins using the official SDK.

The Three Core Components of a Paperclip Plugin

Every Paperclip plugin consists of three mandatory or optional pieces located under packages/plugins/sdk:

  1. Manifest (manifest.ts) — Declares identity, version, capabilities, UI slots, and entrypoints
  2. Worker (worker.ts) — Runs in an isolated process and implements lifecycle hooks via the PluginContext API
  3. UI bundle (ui/…) — Optional React components that render in Paperclip's interface

The Paperclip server spawns a worker process for each installed plugin. Communication happens over JSON-RPC 2.0 on stdio, with the contract defined in packages/plugins/sdk/src/protocol.ts.

Defining the Plugin Manifest

The manifest shape PaperclipPluginManifestV1 in src/manifest.ts drives discovery and security gating.

import type { PaperclipPluginManifestV1 } from "@paperclipai/plugin-sdk";

const manifest: PaperclipPluginManifestV1 = {
  id: "my-awesome-plugin",
  apiVersion: 1,
  version: "0.1.0",
  displayName: "My Awesome Plugin",
  description: "Demo plugin that adds a sidebar link and a detail tab.",
  author: "Your Name",
  categories: ["ui"],
  capabilities: [
    "ui.sidebar.register",
    "ui.detailTab.register",
    "projects.read",
    "plugin.state.read",
  ],
  instanceConfigSchema: {
    type: "object",
    properties: {
      showFeature: {
        type: "boolean",
        title: "Show Feature",
        default: true,
      },
    },
  },
  entrypoints: {
    worker: "./dist/worker.js",
    ui: "./dist/ui",
  },
  ui: {
    slots: [
      {
        type: "projectSidebarItem",
        id: "my-sidebar-link",
        displayName: "My Feature",
        exportName: "MySidebarLink",
        entityTypes: ["project"],
        order: 5,
      },
      {
        type: "detailTab",
        id: "my-detail-tab",
        displayName: "My Tab",
        exportName: "MyDetailTab",
        entityTypes: ["project"],
        order: 5,
      },
    ],
  },
};

export default manifest;

Source: [packages/plugins/examples/plugin-file-browser-example/src/manifest.ts](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/examples/plugin-file-browser-example/src/manifest.ts)

Key Manifest Fields

  • capabilities — Strings that gate what the worker may call on the host, such as ui.sidebar.register, projects.read, or plugin.state.read
  • instanceConfigSchema — JSON Schema describing per-instance settings surfaced in the admin UI
  • entrypoints — Paths to compiled worker (dist/worker.js) and UI bundle (dist/ui)
  • ui.slots — Declares extension points: projectSidebarItem, detailTab, commentAnnotation, and more

Building the Worker with definePlugin

The worker entrypoint calls definePlugin from @paperclipai/plugin-sdk. The only required hook is setup(ctx), where you register handlers.

import { definePlugin } from "@paperclipai/plugin-sdk";

export default definePlugin({
  async setup(ctx) {
    // Register UI data source
    ctx.data.register("my-feature-enabled", async ({ companyId }) => {
      const cfg = await ctx.config.get(companyId);
      return { enabled: cfg.showFeature ?? false };
    });

    // Subscribe to domain events
    ctx.events.on("issue.created", async (event) => {
      const { companyId, payload } = event;
      ctx.logger.info("New issue created", { issueId: payload.id, companyId });
      
      await ctx.http.fetch(`https://api.example.com/notify`, {
        method: "POST",
        body: JSON.stringify({ issueId: payload.id }),
      });
    });

    // Register background job
    ctx.jobs.register("full-sync", async (job) => {
      // Long-running work here
    });
  },

  async onHealth() {
    return { status: "ok", message: "All systems nominal" };
  },
});

Source: [packages/plugins/sdk/src/define-plugin.ts](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sdk/src/define-plugin.ts)

The PluginContext API

The PluginContext interface in packages/plugins/sdk/src/types.ts exposes these capabilities:

API Purpose
ctx.events.on(event, handler) Subscribe to host-emitted domain events
ctx.jobs.register(id, handler) Register background jobs
ctx.data.register(key, fetcher) Provide data to UI components
ctx.config.get(companyId) Resolve company-scoped configuration
ctx.secrets.resolve(ref) Resolve secret references
ctx.http.fetch(url, options) Perform outbound HTTP calls
ctx.state.get/set(params) Read/write scoped key-value state
ctx.logger.info/warn/error Structured logging
ctx.activity.log(entry) Emit audit activity logs
ctx.metrics.write(record) Emit numeric metrics
ctx.telemetry.track(event) Track custom telemetry events
ctx.span.record(span) Record provider spans for tracing

Source: [packages/plugins/sdk/src/types.ts](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sdk/src/types.ts)

Host-Worker RPC Protocol

All method calls are typed in HostToWorkerMethods (host calls worker) and WorkerToHostMethods (worker calls host). The host invokes initialize, configChanged, getData, performAction, and environment* methods. The worker calls back for services like config.get, state.get, and http.fetch.

Errors are namespaced under PLUGIN_RPC_ERROR_CODES in packages/plugins/sdk/src/protocol.ts. For example, CROSS_TENANT_CONFIG fires when a single-tenant plugin receives configuration for a second company.

Source: [packages/plugins/sdk/src/protocol.ts](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sdk/src/protocol.ts)

Creating React UI Components

When your manifest declares UI slots, the compiled UI bundle must export components matching each exportName. The host injects these into the appropriate location and supplies data from ctx.data.register.

import * as React from "react";
import { usePluginData } from "@paperclipai/plugin-sdk";

export const MySidebarLink = () => {
  const { data, loading } = usePluginData("my-feature-enabled");
  if (loading) return null;
  if (!data?.enabled) return null;

  return (
    <a href="#" onClick={() => alert("Clicked My Feature!")}>
      My Feature
    </a>
  );
};

The component name MySidebarLink must match the exportName in your manifest. The host loads the UI bundle under a sandbox that respects your declared capabilities.

Optional: Sandbox Providers for Isolated Execution

For plugins requiring isolated runtimes—such as Docker containers or remote VMs—the SDK defines a sandbox driver interface via environment* methods. Reference implementations live in packages/plugins/sandbox-providers/:

  • novita — Serverless GPU sandbox provider
  • e2b — E2B code sandbox
  • daytona — Daytona workspace environment

Opt in by including environment.* in your capabilities array and implementing the corresponding RPC handlers.

Source: [packages/plugins/sdk/src/protocol.ts](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sdk/src/protocol.ts)

Installing Your Custom Plugin

Build and install from the host CLI:


# Build the plugin (monorepo checkout)

pnpm --filter @paperclipai/plugin-file-browser-example build

# Install via local path (development)

npx paperclipai plugin install ./packages/plugins/examples/plugin-file-browser-example

The host persists your manifest in the database, resolves the worker entrypoint at dist/worker.js, and spawns the process. Uninstalling stops the worker and removes the manifest.

Source: [packages/plugins/examples/plugin-file-browser-example/README.md](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/examples/plugin-file-browser-example/README.md)

Summary

  • Paperclip plugin architecture consists of a manifest, worker process, and optional UI bundle
  • The manifest (manifest.ts) declares capabilities that gate host API access and UI slots that determine where components render
  • The worker uses definePlugin from @paperclipai/plugin-sdk to register event handlers, jobs, data providers, and lifecycle hooks
  • JSON-RPC 2.0 over stdio enables typed, bidirectional communication between host and worker
  • The PluginContext API in packages/plugins/sdk/src/types.ts provides 12+ subsystems for configuration, state, HTTP, logging, metrics, and more
  • UI components receive data via ctx.data.register and render inside capability-restricted sandboxes
  • Sandbox providers enable isolated execution environments for security-sensitive operations

Frequently Asked Questions

How does Paperclip isolate plugins from the main application?

Paperclip spawns each plugin in a separate worker process and communicates over JSON-RPC on stdio. The host enforces capability-based access control—plugins can only call host methods explicitly listed in their manifest's capabilities array. Sandboxed execution environments are available for additional isolation.

What programming language are Paperclip plugins written in?

Plugins are written in TypeScript and compiled to JavaScript. The SDK (@paperclipai/plugin-sdk) provides full type safety for the manifest, context APIs, and RPC protocol. UI components use React with hooks provided by the SDK.

Can a plugin access data across multiple companies or tenants?

No. The plugin system enforces tenant isolation. If a single-tenant plugin receives configuration for a second company, the host returns PLUGIN_RPC_ERROR_CODES.CROSS_TENANT_CONFIG. The PluginContext automatically scopes state, configuration, and secrets to the requesting company's ID.

Where can I find complete working examples of Paperclip plugins?

The repository includes packages/plugins/examples/plugin-file-browser-example/ demonstrating sidebar items and detail tabs. For workspace manipulation examples, see packages/plugins/plugin-workspace-diff/src/. Sandbox provider implementations are in packages/plugins/sandbox-providers/.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →