openclaw/docs/contributing/plugins.md
ronitchidara 8e00cbf27d community: add Phase 6 marketplace integration and contribution guides
ClawdHub Web UI Integration:
- Add ClawdHub API client (search, details, install, updates)
- Add Gateway RPC handlers with auth scopes
- Add Marketplace tab with browse/installed views
- Add state management and Lit HTML components

Documentation:
- Add feature maturity matrix classifying 100+ features
- Add skills contribution guide (SKILL.md format, gating, publishing)
- Add plugins contribution guide (manifest, API, distribution)
- Update CONTRIBUTING.md with links to new guides
- Add Contributing section to docs navigation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 17:01:41 +05:30

14 KiB

title description
Contributing Plugins How to create, test, and distribute Clawdbot plugins

Contributing Plugins

This guide walks you through creating plugins that extend Clawdbot with new channels, tools, commands, and services.

What is a Plugin?

A plugin is a TypeScript module that extends Clawdbot at runtime. Plugins can:

  • Register new messaging channels (like Matrix, Nostr, MS Teams)
  • Add agent tools the AI can invoke
  • Register Gateway RPC methods
  • Add CLI commands
  • Run background services
  • Ship bundled skills
  • Register auto-reply commands

Plugins run in-process with the Gateway, so treat them as trusted code.

Quick Start

Create a minimal plugin in 5 minutes:

# Create plugin directory
mkdir -p ~/.clawdbot/extensions/hello-world

# Create the manifest
cat > ~/.clawdbot/extensions/hello-world/clawdbot.plugin.json << 'EOF'
{
  "id": "hello-world",
  "name": "Hello World",
  "description": "A simple example plugin",
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "greeting": {
        "type": "string",
        "default": "Hello"
      }
    }
  }
}
EOF

# Create the plugin code
cat > ~/.clawdbot/extensions/hello-world/index.ts << 'EOF'
export default function register(api) {
  const config = api.pluginConfig ?? {};
  const greeting = config.greeting ?? "Hello";

  // Register a Gateway RPC method
  api.registerGatewayMethod("hello.greet", ({ params, respond }) => {
    const name = params?.name ?? "World";
    respond(true, { message: `${greeting}, ${name}!` });
  });

  // Register an auto-reply command
  api.registerCommand({
    name: "hello",
    description: "Say hello",
    handler: () => ({ text: `${greeting} from the plugin!` }),
  });

  api.logger.info("[hello-world] Plugin loaded");
}
EOF

Restart the Gateway to load your plugin.

Plugin Structure

Directory Layout

my-plugin/
├── clawdbot.plugin.json    # Required: manifest
├── index.ts                # Required: entry point
├── package.json            # Optional: for npm distribution
├── src/                    # Optional: source files
│   ├── cli.ts
│   ├── tools.ts
│   └── types.ts
├── skills/                 # Optional: bundled skills
│   └── my-skill/
│       └── SKILL.md
└── README.md

Plugin Manifest (clawdbot.plugin.json)

Every plugin must have a manifest:

{
  "id": "my-plugin",
  "name": "My Plugin",
  "description": "Does amazing things",
  "version": "1.0.0",
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "apiKey": { "type": "string" },
      "enabled": { "type": "boolean" }
    }
  },
  "uiHints": {
    "apiKey": { "label": "API Key", "sensitive": true },
    "enabled": { "label": "Enable Feature" }
  }
}

Required Fields

Field Type Description
id string Unique plugin identifier
configSchema object JSON Schema for config validation

Optional Fields

Field Type Description
name string Display name
description string Short summary
version string Semantic version
kind string Plugin category (e.g., "memory")
channels string[] Channel IDs this plugin registers
providers string[] Provider IDs this plugin registers
skills string[] Skill directories to load
uiHints object UI labels and hints

Entry Point (index.ts)

Plugins export either a function or an object:

// Function style (simple)
export default function register(api) {
  // Registration code
}

// Object style (with metadata)
export default {
  id: "my-plugin",
  name: "My Plugin",
  configSchema: { /* ... */ },
  register(api) {
    // Registration code
  },
};

Plugin API

The api object provides access to Clawdbot internals:

Core Properties

api.config       // Full Clawdbot configuration
api.pluginConfig // This plugin's config (plugins.entries.<id>.config)
api.logger       // Scoped logger instance
api.runtime      // Runtime helpers (TTS, etc.)

Registration Methods

Gateway RPC Methods

api.registerGatewayMethod("myplugin.action", async ({ params, respond }) => {
  // params: request parameters
  // respond(ok: boolean, payload?: any): send response

  try {
    const result = await doSomething(params);
    respond(true, { data: result });
  } catch (err) {
    respond(false, { error: err.message });
  }
});

Agent Tools

import { Type } from "@sinclair/typebox";

api.registerTool({
  name: "my_tool",
  description: "Does something useful",
  inputSchema: Type.Object({
    query: Type.String({ description: "Search query" }),
    limit: Type.Optional(Type.Number({ description: "Max results" })),
  }),
  handler: async ({ params, context }) => {
    const results = await search(params.query, params.limit);
    return { results };
  },
});

CLI Commands

api.registerCli(({ program }) => {
  program
    .command("myplugin")
    .description("My plugin command")
    .option("-v, --verbose", "Verbose output")
    .action((options) => {
      console.log("Running with options:", options);
    });
}, { commands: ["myplugin"] });

Auto-Reply Commands

Commands that respond without invoking the AI:

api.registerCommand({
  name: "mystatus",
  description: "Show plugin status",
  acceptsArgs: false,
  requireAuth: true,
  handler: (ctx) => ({
    text: `Plugin active on ${ctx.channel}`,
  }),
});

Handler context:

  • ctx.senderId - Sender ID
  • ctx.channel - Channel name
  • ctx.isAuthorizedSender - Auth status
  • ctx.args - Command arguments (if acceptsArgs: true)
  • ctx.config - Clawdbot config

Background Services

api.registerService({
  id: "my-background-service",
  start: async () => {
    api.logger.info("Service starting");
    // Initialize background work
  },
  stop: async () => {
    api.logger.info("Service stopping");
    // Cleanup
  },
});

Messaging Channels

api.registerChannel({
  plugin: {
    id: "mychannel",
    meta: {
      id: "mychannel",
      label: "My Channel",
      selectionLabel: "My Channel (API)",
      docsPath: "/channels/mychannel",
      blurb: "Custom messaging channel",
      aliases: ["mc"],
    },
    capabilities: { chatTypes: ["direct", "group"] },
    config: {
      listAccountIds: (cfg) =>
        Object.keys(cfg.channels?.mychannel?.accounts ?? {}),
      resolveAccount: (cfg, accountId) =>
        cfg.channels?.mychannel?.accounts?.[accountId ?? "default"],
    },
    outbound: {
      deliveryMode: "direct",
      sendText: async ({ text, target }) => {
        await deliverMessage(text, target);
        return { ok: true };
      },
    },
  },
});

Model Providers

api.registerProvider({
  id: "acme",
  label: "AcmeAI",
  auth: [
    {
      id: "oauth",
      label: "OAuth Login",
      kind: "oauth",
      run: async (ctx) => {
        // OAuth flow
        return {
          profiles: [{ profileId: "acme:default", credential: { ... } }],
          defaultModel: "acme/model-1",
        };
      },
    },
  ],
});

Configuration

User Configuration

Users configure your plugin in ~/.clawdbot/clawdbot.json:

{
  plugins: {
    entries: {
      "my-plugin": {
        enabled: true,
        config: {
          apiKey: "sk-xxx",
          endpoint: "https://api.example.com"
        }
      }
    }
  }
}

Config Schema

Define validation rules in the manifest:

{
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "required": ["apiKey"],
    "properties": {
      "apiKey": {
        "type": "string",
        "minLength": 1
      },
      "endpoint": {
        "type": "string",
        "format": "uri"
      },
      "timeout": {
        "type": "integer",
        "minimum": 1000,
        "default": 30000
      }
    }
  }
}

UI Hints

Help the Control UI render better forms:

{
  "uiHints": {
    "apiKey": {
      "label": "API Key",
      "placeholder": "sk-...",
      "sensitive": true
    },
    "endpoint": {
      "label": "API Endpoint",
      "help": "Custom endpoint URL",
      "advanced": true
    },
    "timeout": {
      "label": "Timeout (ms)",
      "advanced": true
    }
  }
}

UI hint fields:

  • label - Display label
  • placeholder - Input placeholder
  • help - Help text
  • sensitive - Mark as password field
  • advanced - Hide in basic view

Bundling Skills

Plugins can ship skills:

{
  "id": "my-plugin",
  "skills": ["./skills/my-skill"]
}

Skills are loaded when the plugin is enabled and follow standard skill precedence.

Plugin Hooks

Plugins can register event hooks:

import { registerPluginHooksFromDir } from "clawdbot/plugin-sdk";

export default function register(api) {
  registerPluginHooksFromDir(api, "./hooks");
}

Hook directories follow the standard hook structure (HOOK.md + handler.ts).

Testing

Local Testing

  1. Create your plugin in ~/.clawdbot/extensions/
  2. Restart the Gateway
  3. Check it loaded: clawdbot plugins list
  4. Test your features

Development Mode

Link your plugin for live development:

clawdbot plugins install -l ./my-plugin

The -l flag creates a symlink instead of copying.

Unit Tests

Ship tests with your plugin:

// src/my-plugin.test.ts
import { describe, it, expect } from "vitest";
import { myFunction } from "./my-function.js";

describe("myFunction", () => {
  it("should process input correctly", () => {
    expect(myFunction("test")).toBe("expected");
  });
});

Run with Vitest or your preferred test runner.

Distribution

npm Publishing

Package your plugin for npm:

// package.json
{
  "name": "@yourname/clawdbot-my-plugin",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": ["dist", "clawdbot.plugin.json"],
  "clawdbot": {
    "extensions": ["./dist/index.js"]
  },
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  }
}

Users install with:

clawdbot plugins install @yourname/clawdbot-my-plugin

Local Distribution

Distribute as a tarball or zip:

# Create archive
tar -czf my-plugin.tar.gz my-plugin/

# Install
clawdbot plugins install ./my-plugin.tar.gz

Best Practices

Naming

  • Plugin ID: kebab-case (my-plugin)
  • npm package: @scope/clawdbot-* or clawdbot-*
  • RPC methods: pluginid.action
  • Tools: snake_case

Error Handling

api.registerGatewayMethod("myplugin.action", async ({ params, respond }) => {
  try {
    // Validate required params
    if (!params?.id) {
      respond(false, { error: "id is required" });
      return;
    }

    const result = await doWork(params.id);
    respond(true, { data: result });
  } catch (err) {
    api.logger.error("[myplugin] Action failed:", err);
    respond(false, {
      error: err instanceof Error ? err.message : "Unknown error"
    });
  }
});

Logging

Use the scoped logger:

api.logger.debug("[myplugin] Debug info");
api.logger.info("[myplugin] Starting");
api.logger.warn("[myplugin] Warning message");
api.logger.error("[myplugin] Error:", error);

Configuration Defaults

Handle missing config gracefully:

const config = api.pluginConfig ?? {};
const timeout = config.timeout ?? 30000;
const endpoint = config.endpoint ?? "https://api.example.com";

Deprecation Warnings

if (config.oldOption !== undefined) {
  api.logger.warn(
    "[myplugin] 'oldOption' is deprecated; use 'newOption' instead"
  );
}

Cleanup

Always clean up resources:

let interval: NodeJS.Timeout | null = null;

api.registerService({
  id: "my-service",
  start: () => {
    interval = setInterval(checkStatus, 60000);
  },
  stop: () => {
    if (interval) {
      clearInterval(interval);
      interval = null;
    }
  },
});

Example Plugins

Browse official plugins for patterns:

Plugin Description Key Patterns
voice-call Phone calls RPC methods, tools, CLI, config schema
matrix Matrix channel Channel registration, E2EE
memory-lancedb Vector memory Plugin slots, background service
lobster Natural CLI Tool registration

Troubleshooting

Plugin not loading

  1. Check clawdbot plugins list
  2. Verify manifest exists: clawdbot.plugin.json
  3. Check for syntax errors in manifest JSON
  4. Look for errors in logs: clawdbot logs -f
  5. Run doctor: clawdbot doctor

Config validation errors

  1. Verify JSON Schema in manifest
  2. Check additionalProperties: false blocks unknown keys
  3. Test config against schema with a JSON validator

RPC method not found

  1. Verify method name matches registration
  2. Check plugin is enabled in config
  3. Restart Gateway after changes

Tool not appearing

  1. Verify tool registration code runs
  2. Check tool name doesn't conflict
  3. Verify schema is valid (no anyOf/oneOf)

Resources