Skip to main content

Plugin SDK

The SimpleClaw Plugin SDK enables you to extend the Gateway with custom channels, providers, and tools.

Plugin Types

SimpleClaw supports two plugin categories:
  1. Channel Plugins - Messaging platform integrations (WhatsApp, Telegram, etc.)
  2. Provider Plugins - LLM providers, memory systems, task runners

Installation

The Plugin SDK is included with SimpleClaw:
import {
  // Channel plugin types
  type ChannelPlugin,
  type ChannelConfigAdapter,
  type ChannelOutboundAdapter,
  
  // Provider plugin types
  type SimpleClawPluginApi,
  type ProviderAuthContext,
  
  // Utilities
  buildMediaPayload,
  normalizeWebhookPath,
  registerWebhookTarget,
} from "simpleclaw/plugin-sdk";

Channel Plugin Structure

Basic Example

import type { ChannelPlugin } from "simpleclaw/plugin-sdk";

export const customChannel: ChannelPlugin = {
  id: "custom",
  
  meta: {
    label: "Custom Chat",
    detailLabel: "Custom messaging platform",
    systemImage: "message.circle",
  },
  
  capabilities: {
    outbound: true,
    groups: true,
    threads: false,
    media: ["image", "video", "audio"],
  },
  
  config: {
    // Configuration adapter
    resolveDefaultAccountId: (config) => "default",
    resolveAccount: (config, accountId) => ({
      accountId,
      enabled: true,
    }),
  },
  
  outbound: {
    // Message sending
    async send(ctx, message) {
      // Send message to platform API
      await fetch(`https://api.example.com/send`, {
        method: "POST",
        body: JSON.stringify({
          to: message.to,
          text: message.text,
        }),
      });
    },
  },
  
  status: {
    // Health checks
    async buildAccountSnapshot(ctx, accountId) {
      return {
        accountId,
        configured: true,
        connected: true,
        running: true,
      };
    },
  },
};

Plugin SDK Exports

The SDK provides helpers for common plugin tasks:

Media Handling

import { buildMediaPayload, type MediaPayload } from "simpleclaw/plugin-sdk";

// Convert media to standard format
const media: MediaPayload = buildMediaPayload({
  url: "https://example.com/image.jpg",
  mime: "image/jpeg",
  size: 12345,
  filename: "photo.jpg",
});

Webhook Registration

import {
  registerWebhookTarget,
  normalizeWebhookPath,
} from "simpleclaw/plugin-sdk";

// Register webhook endpoint
registerWebhookTarget({
  channelId: "custom",
  accountId: "default",
  path: normalizeWebhookPath("/webhooks/custom"),
  handler: async (req, res) => {
    const body = await req.json();
    // Process webhook
    res.json({ ok: true });
  },
});

Account Helpers

import { createAccountListHelpers } from "simpleclaw/plugin-sdk";

const helpers = createAccountListHelpers({
  configPrefix: "channels.custom",
  accountSchema: AccountConfigSchema,
});

// List all configured accounts
const accountIds = helpers.listAccountIds(config);

Status Helpers

import {
  buildBaseAccountStatusSnapshot,
  collectStatusIssuesFromLastError,
} from "simpleclaw/plugin-sdk";

// Build standard account snapshot
const snapshot = buildBaseAccountStatusSnapshot({
  accountId: "default",
  enabled: true,
  configured: true,
  connected: state.connected,
  lastError: state.error?.message,
});

Configuration Schema

Define config schema with Zod:
import { z } from "zod";
import { buildChannelConfigSchema } from "simpleclaw/plugin-sdk";

const AccountConfigSchema = z.object({
  apiKey: z.string(),
  webhookUrl: z.string().optional(),
});

const CustomConfigSchema = z.object({
  enabled: z.boolean().default(false),
  accounts: z.record(AccountConfigSchema),
});

export const configSchema = buildChannelConfigSchema({
  schema: CustomConfigSchema.shape,
  uiHints: {
    "accounts.*.apiKey": {
      label: "API Key",
      sensitive: true,
      help: "Get this from Custom Chat dashboard",
    },
  },
});

Outbound Message Delivery

import type { ChannelOutboundAdapter } from "simpleclaw/plugin-sdk";

export const outbound: ChannelOutboundAdapter = {
  async send(ctx, message) {
    const { to, text, media } = message;
    const account = ctx.account;
    
    // Send text message
    if (text) {
      await api.sendText({
        apiKey: account.apiKey,
        recipient: to,
        message: text,
      });
    }
    
    // Send media attachments
    if (media?.length) {
      for (const item of media) {
        await api.sendMedia({
          apiKey: account.apiKey,
          recipient: to,
          url: item.url,
          caption: item.caption,
        });
      }
    }
  },
  
  async sendReaction(ctx, params) {
    await api.react({
      apiKey: ctx.account.apiKey,
      messageId: params.messageId,
      emoji: params.emoji,
    });
  },
};

Security Adapter

Implement allowlist and DM policy:
import type { ChannelSecurityAdapter } from "simpleclaw/plugin-sdk";
import { isNormalizedSenderAllowed } from "simpleclaw/plugin-sdk";

export const security: ChannelSecurityAdapter = {
  resolveSenderAllowed: (ctx, sender) => {
    const allowFrom = ctx.account.allowFrom || [];
    
    return isNormalizedSenderAllowed({
      senderId: sender.id,
      allowFrom,
    });
  },
  
  resolveDmPolicy: (ctx) => {
    return ctx.account.dmPolicy || "pairing";
  },
};

Gateway Adapter

Handle incoming messages:
import type { ChannelGatewayAdapter } from "simpleclaw/plugin-sdk";

export const gateway: ChannelGatewayAdapter = {
  async start(ctx) {
    const account = ctx.account;
    
    // Connect to platform API
    const client = await connect(account.apiKey);
    
    // Listen for incoming messages
    client.on("message", async (msg) => {
      await ctx.handleInbound({
        from: msg.sender.id,
        text: msg.text,
        timestamp: msg.timestamp,
      });
    });
  },
  
  async stop(ctx) {
    // Cleanup
    await client?.disconnect();
  },
};

Plugin Packaging

Create a plugin package:
{
  "name": "@simpleclaw/channel-custom",
  "version": "1.0.0",
  "main": "dist/index.js",
  "simpleclaw": {
    "extensions": [
      {
        "type": "channel",
        "id": "custom",
        "entry": "./dist/index.js"
      }
    ]
  },
  "dependencies": {
    "simpleclaw": "^2026.3.0"
  }
}

Provider Plugin Example

import type {
  SimpleClawPluginService,
  SimpleClawPluginServiceContext,
} from "simpleclaw/plugin-sdk";

export const customProvider: SimpleClawPluginService = {
  id: "custom-llm",
  
  async init(ctx: SimpleClawPluginServiceContext) {
    // Initialize provider
  },
  
  async auth(ctx) {
    // OAuth flow or API key validation
    return {
      success: true,
      profile: {
        id: "user-123",
        name: "User Name",
      },
    };
  },
  
  async invoke(ctx, request) {
    // Handle LLM request
    const response = await fetch("https://api.example.com/chat", {
      method: "POST",
      body: JSON.stringify(request),
    });
    
    return response.json();
  },
};

Testing Plugins

import { describe, test, expect } from "vitest";
import { customChannel } from "./index.js";

describe("custom channel", () => {
  test("sends messages", async () => {
    const ctx = createMockContext();
    
    await customChannel.outbound.send(ctx, {
      to: "user-123",
      text: "Hello!",
    });
    
    expect(mockApi.sendText).toHaveBeenCalledWith({
      recipient: "user-123",
      message: "Hello!",
    });
  });
});

Next Steps

Channel Plugins

Deep dive into channel plugin development

Provider Plugins

Build custom LLM and tool providers

Build docs developers (and LLMs) love