Skip to content

Building Plugins

Plugins extend OpenClaw with new capabilities: channels, model providers, speech, image generation, web search, agent tools, or any combination.

You do not need to add your plugin to the OpenClaw repository. Publish to ClawHub or npm and users install with openclaw plugins install <package-name>. OpenClaw tries ClawHub first and falls back to npm automatically.

  • Node >= 22 and a package manager (npm or pnpm)
  • Familiarity with TypeScript (ESM)
  • For in-repo plugins: repository cloned and pnpm install done
Channel plugin

Connect OpenClaw to a messaging platform (Discord, IRC, etc.)

Provider plugin

Add a model provider (LLM, proxy, or custom endpoint)

Tool / hook plugin

Register agent tools, event hooks, or services — continue below

This walkthrough creates a minimal plugin that registers an agent tool. Channel and provider plugins have dedicated guides linked above.

  1. Create the package and manifest

    {
    "name": "@myorg/openclaw-my-plugin",
    "version": "1.0.0",
    "type": "module",
    "openclaw": {
    "extensions": ["./index.ts"],
    "compat": {
    "pluginApi": ">=2026.3.24-beta.2",
    "minGatewayVersion": "2026.3.24-beta.2"
    },
    "build": {
    "openclawVersion": "2026.3.24-beta.2",
    "pluginSdkVersion": "2026.3.24-beta.2"
    }
    }
    }
    {
    "id": "my-plugin",
    "name": "My Plugin",
    "description": "Adds a custom tool to OpenClaw",
    "configSchema": {
    "type": "object",
    "additionalProperties": false
    }
    }

    Every plugin needs a manifest, even with no config. See Manifest for the full schema. The canonical ClawHub publish snippets live in docs/snippets/plugin-publish/.

  2. Write the entry point

    index.ts
    import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
    import { Type } from "@sinclair/typebox";
    export default definePluginEntry({
    id: "my-plugin",
    name: "My Plugin",
    description: "Adds a custom tool to OpenClaw",
    register(api) {
    api.registerTool({
    name: "my_tool",
    description: "Do a thing",
    parameters: Type.Object({ input: Type.String() }),
    async execute(_id, params) {
    return { content: [{ type: "text", text: `Got: ${params.input}` }] };
    },
    });
    },
    });

    definePluginEntry is for non-channel plugins. For channels, use defineChannelPluginEntry — see Channel Plugins. For full entry point options, see Entry Points.

  3. Test and publish

    External plugins: validate and publish with ClawHub, then install:

    Terminal window
    clawhub package publish your-org/your-plugin --dry-run
    clawhub package publish your-org/your-plugin
    openclaw plugins install clawhub:@myorg/openclaw-my-plugin

    OpenClaw also checks ClawHub before npm for bare package specs like @myorg/openclaw-my-plugin.

    In-repo plugins: place under the bundled plugin workspace tree — automatically discovered.

    Terminal window
    pnpm test --

    /my-plugin/ ```

A single plugin can register any number of capabilities via the api object:

CapabilityRegistration methodDetailed guide
Text inference (LLM)api.registerProvider(...)Provider Plugins
CLI inference backendapi.registerCliBackend(...)CLI Backends
Channel / messagingapi.registerChannel(...)Channel Plugins
Speech (TTS/STT)api.registerSpeechProvider(...)Provider Plugins
Media understandingapi.registerMediaUnderstandingProvider(...)Provider Plugins
Image generationapi.registerImageGenerationProvider(...)Provider Plugins
Web searchapi.registerWebSearchProvider(...)Provider Plugins
Agent toolsapi.registerTool(...)Below
Custom commandsapi.registerCommand(...)Entry Points
Event hooksapi.registerHook(...)Entry Points
HTTP routesapi.registerHttpRoute(...)Internals
CLI subcommandsapi.registerCli(...)Entry Points

For the full registration API, see SDK Overview.

Hook guard semantics to keep in mind:

  • before_tool_call: { block: true } is terminal and stops lower-priority handlers.
  • before_tool_call: { block: false } is treated as no decision.
  • before_tool_call: { requireApproval: true } pauses agent execution and prompts the user for approval via the exec approval overlay, Telegram buttons, Discord interactions, or the /approve command on any channel.
  • before_install: { block: true } is terminal and stops lower-priority handlers.
  • before_install: { block: false } is treated as no decision.
  • message_sending: { cancel: true } is terminal and stops lower-priority handlers.
  • message_sending: { cancel: false } is treated as no decision.

The /approve command handles both exec and plugin approvals with automatic fallback. Plugin approval forwarding can be configured independently via approvals.plugin in config.

See SDK Overview hook decision semantics for details.

Tools are typed functions the LLM can call. They can be required (always available) or optional (user opt-in):

register(api) {
// Required tool — always available
api.registerTool({
name: "my_tool",
description: "Do a thing",
parameters: Type.Object({ input: Type.String() }),
async execute(_id, params) {
return { content: [{ type: "text", text: params.input }] };
},
});
// Optional tool — user must add to allowlist
api.registerTool(
{
name: "workflow_tool",
description: "Run a workflow",
parameters: Type.Object({ pipeline: Type.String() }),
async execute(_id, params) {
return { content: [{ type: "text", text: params.pipeline }] };
},
},
{ optional: true },
);
}

Users enable optional tools in config:

{
tools: { allow: ["workflow_tool"] },
}
  • Tool names must not clash with core tools (conflicts are skipped)
  • Use optional: true for tools with side effects or extra binary requirements
  • Users can enable all tools from a plugin by adding the plugin id to tools.allow

Always import from focused `openclaw/plugin-sdk/

` paths:

import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
// Wrong: monolithic root (deprecated, will be removed)
import { ... } from "openclaw/plugin-sdk";

For the full subpath reference, see SDK Overview.

Within your plugin, use local barrel files (api.ts, runtime-api.ts) for internal imports — never import your own plugin through its SDK path.

  1. Watch for GitHub release tags on openclaw/openclaw and subscribe via Watch > Releases. Beta tags look like v2026.3.N-beta.1. You can also turn on notifications for the official OpenClaw X account @openclaw for release announcements.
  2. Test your plugin against the beta tag as soon as it appears. The window before stable is typically only a few hours.
  3. Post in your plugin’s thread in the plugin-forum Discord channel after testing with either all good or what broke. If you do not have a thread yet, create one.
  4. If something breaks, open or update an issue titled `Beta blocker:

and apply thebeta-blockerlabel. Put the issue link in your thread. 5. Open a PR tomaintitledfix(

): beta blocker -

` and link the issue in both the PR and your Discord thread. Contributors cannot label PRs, so the title is the PR-side signal for maintainers and automation. Blockers with a PR get merged; blockers without one might ship anyway. Maintainers watch these threads during beta testing. 6. Silence means green. If you miss the window, your fix likely lands in the next cycle.

Channel Plugins

Build a messaging channel plugin

Provider Plugins

Build a model provider plugin

SDK Overview

Import map and registration API reference

Runtime Helpers

TTS, search, subagent via api.runtime

Testing

Test utilities and patterns

Plugin Manifest

Full manifest schema reference