Building plugins

Building plugins

Plugins extend OpenClaw without changing core. A plugin can add a messaging channel, model provider, local CLI backend, agent tool, hook, media provider, or another plugin-owned capability.

You do not need to add an external plugin to the OpenClaw repository. Publish the package to ClawHub and users install it with:

bash
openclaw plugins install clawhub:<package-name>

Bare package specs still install from npm during the launch cutover. Use the clawhub: prefix when you want ClawHub resolution.

Requirements

  • Use Node 22.19 or newer and a package manager such as npm or pnpm.
  • Be familiar with TypeScript ESM modules.
  • For in-repo bundled plugin work, clone the repository and run pnpm install. Source-checkout plugin development is pnpm-only because OpenClaw loads bundled plugins from extensions/* workspace packages.

Choose the plugin shape

Quickstart

Build a minimal tool plugin by registering one required agent tool. This is the shortest useful plugin shape and shows the package, manifest, entry point, and local proof.

  • Create package metadata

    package.json
    {"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"}}}
    openclaw.plugin.json
    {"id": "my-plugin","name": "My Plugin","description": "Adds a custom tool to OpenClaw","contracts": {"tools": ["my_tool"]},"activation": {"onStartup": true},"configSchema": {"type": "object","additionalProperties": false}}

    Published external plugins should point runtime entries at built JavaScript files. See SDK entry points for the full entry point contract.

    Every plugin needs a manifest, even when it has no config. Runtime tools must appear in contracts.tools so OpenClaw can discover ownership without eagerly loading every plugin runtime. Set activation.onStartup intentionally. This example starts on Gateway startup.

    Host-trusted plugin surfaces are also manifest-gated and require explicit enablement for installed plugins. If an installed plugin registers api.registerAgentToolResultMiddleware(...), declare each target runtime in contracts.agentToolResultMiddleware. If it registers api.registerTrustedToolPolicy(...), declare each policy id in contracts.trustedToolPolicies. These declarations keep install-time inspection and runtime registration aligned.

    For every manifest field, see Plugin manifest.

  • Register the tool

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

    Use definePluginEntry for non-channel plugins. Channel plugins use defineChannelPluginEntry.

  • Test the runtime

    For an installed or external plugin, inspect the loaded runtime:

    bash
    openclaw plugins inspect my-plugin --runtime --json

    If the plugin registers a CLI command, run that command too. For example, a demo command should have an execution proof such as openclaw demo-plugin ping.

    For a bundled plugin in this repository, OpenClaw discovers source-checkout plugin packages from the extensions/* workspace. Run the closest targeted test:

    bash
    pnpm test -- extensions/my-plugin/pnpm check
  • Publish

    Validate the package before publishing:

    bash
    clawhub package publish your-org/your-plugin --dry-runclawhub package publish your-org/your-plugin

    The canonical ClawHub snippets live in docs/snippets/plugin-publish/.

  • Install

    Install the published package through ClawHub:

    bash
    openclaw plugins install clawhub:your-org/your-plugin
  • Registering tools

    Tools can be required or optional. Required tools are always available when the plugin is enabled. Optional tools require user opt-in.

    typescript
    register(api) {  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 },  );}

    Every tool registered with api.registerTool(...) must also be declared in the plugin manifest:

    json
    {  "contracts": {    "tools": ["workflow_tool"]  },  "toolMetadata": {    "workflow_tool": {      "optional": true    }  }}

    Users opt in with tools.allow:

    json5
    {  tools: { allow: ["workflow_tool"] }, // or ["my-plugin"] for all tools from one plugin}

    Optional tools control whether a tool is exposed to the model. Use plugin permission requests when a tool or hook should ask for approval after the model selects it and before the action runs.

    Use optional tools for side effects, unusual binaries, or capabilities that should not be exposed by default. Tool names must not conflict with core tools; conflicts are skipped and reported in plugin diagnostics. Malformed registrations, including tool descriptors without parameters, are skipped and reported the same way. Registered tools are typed functions the model can call after policy and allowlist checks pass.

    Tool factories receive a runtime-supplied context object. Use ctx.activeModel when a tool needs to log, display, or adapt to the active model for the current turn. The object can include provider, modelId, and modelRef. Treat it as informational runtime metadata, not as a security boundary against the local operator, installed plugin code, or a modified OpenClaw runtime. Sensitive local tools should still require an explicit plugin or operator opt-in and fail closed when active-model metadata is missing or unsuitable.

    The manifest declares ownership and discovery; execution still calls the live registered tool implementation. Keep toolMetadata.<tool>.optional: true aligned with api.registerTool(..., { optional: true }) so OpenClaw can avoid loading that plugin runtime until the tool is explicitly allowlisted.

    Import conventions

    Import from focused SDK subpaths:

    typescript
      

    Do not import from the deprecated root barrel:

    typescript
     

    Within your plugin package, use local barrel files such as api.ts and runtime-api.ts for internal imports. Do not import your own plugin through an SDK path. Provider-specific helpers should stay in the provider package unless the seam is truly generic.

    Custom Gateway RPC methods are an advanced entry point. Keep them on a plugin-specific prefix; core admin namespaces such as config.*, exec.approvals.*, operator.admin.*, wizard.*, and update.* stay reserved and resolve to operator.admin. The openclaw/plugin-sdk/gateway-method-runtime bridge is reserved for plugin HTTP routes that declare contracts.gatewayMethodDispatch: ["authenticated-request"].

    For the full import map, see Plugin SDK overview.

    Pre-submission checklist

    OPENCLAW_DOCS_MARKER:calloutOpen:Q2hlY2s package.json has correct openclaw metadata OPENCLAW_DOCS_MARKER:calloutClose:

    OPENCLAW_DOCS_MARKER:calloutOpen:Q2hlY2s openclaw.plugin.json manifest is present and valid OPENCLAW_DOCS_MARKER:calloutClose:

    OPENCLAW_DOCS_MARKER:calloutOpen:Q2hlY2s Entry point uses defineChannelPluginEntry or definePluginEntry OPENCLAW_DOCS_MARKER:calloutClose:

    OPENCLAW_DOCS_MARKER:calloutOpen:Q2hlY2s All imports use focused plugin-sdk/<subpath> paths OPENCLAW_DOCS_MARKER:calloutClose:

    Was this useful?
    On this page

    On this page