--- title: "Building Plugins" sidebarTitle: "Building Plugins" summary: "Step-by-step guide for creating OpenClaw plugins with any combination of capabilities" read_when: - You want to create a new OpenClaw plugin - You need to understand the plugin SDK import patterns - You are adding a new channel, provider, tool, or other capability to OpenClaw --- # Building Plugins Plugins extend OpenClaw with new capabilities: channels, model providers, speech, image generation, web search, agent tools, or any combination. A single plugin can register multiple capabilities. OpenClaw encourages **external plugin development**. You do not need to add your plugin to the OpenClaw repository. Publish your plugin on npm, and users install it with `openclaw plugins install `. OpenClaw also maintains a set of core plugins in-repo, but the plugin system is designed for independent ownership and distribution. ## Prerequisites - Node >= 22 and a package manager (npm or pnpm) - Familiarity with TypeScript (ESM) - For in-repo plugins: OpenClaw repository cloned and `pnpm install` done ## Plugin capabilities A plugin can register one or more capabilities. The capability you register determines what your plugin provides to OpenClaw: | Capability | Registration method | What it adds | | ------------------- | --------------------------------------------- | ------------------------------ | | Text inference | `api.registerProvider(...)` | Model provider (LLM) | | Channel / messaging | `api.registerChannel(...)` | Chat channel (e.g. Slack, IRC) | | Speech | `api.registerSpeechProvider(...)` | Text-to-speech / STT | | Media understanding | `api.registerMediaUnderstandingProvider(...)` | Image/audio/video analysis | | Image generation | `api.registerImageGenerationProvider(...)` | Image generation | | Web search | `api.registerWebSearchProvider(...)` | Web search provider | | Agent tools | `api.registerTool(...)` | Tools callable by the agent | A plugin that registers zero capabilities but provides hooks or services is a **hook-only** plugin. That pattern is still supported. ## Plugin structure Plugins follow this layout (whether in-repo or standalone): ``` my-plugin/ ├── package.json # npm metadata + openclaw config ├── openclaw.plugin.json # Plugin manifest ├── index.ts # Entry point ├── setup-entry.ts # Setup wizard (optional) ├── api.ts # Public exports (optional) ├── runtime-api.ts # Internal exports (optional) └── src/ ├── provider.ts # Capability implementation ├── runtime.ts # Runtime wiring └── *.test.ts # Colocated tests ``` ## Create a plugin Create `package.json` with the `openclaw` metadata block. The structure depends on what capabilities your plugin provides. **Channel plugin example:** ```json { "name": "@myorg/openclaw-my-channel", "version": "1.0.0", "type": "module", "openclaw": { "extensions": ["./index.ts"], "channel": { "id": "my-channel", "label": "My Channel", "blurb": "Short description of the channel." } } } ``` **Provider plugin example:** ```json { "name": "@myorg/openclaw-my-provider", "version": "1.0.0", "type": "module", "openclaw": { "extensions": ["./index.ts"], "providers": ["my-provider"] } } ``` The `openclaw` field tells the plugin system what your plugin provides. A plugin can declare both `channel` and `providers` if it provides multiple capabilities. The entry point registers your capabilities with the plugin API. **Channel plugin:** ```typescript import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core"; export default defineChannelPluginEntry({ id: "my-channel", name: "My Channel", description: "Connects OpenClaw to My Channel", plugin: { // Channel adapter implementation }, }); ``` **Provider plugin:** ```typescript import { definePluginEntry } from "openclaw/plugin-sdk/core"; export default definePluginEntry({ id: "my-provider", name: "My Provider", register(api) { api.registerProvider({ // Provider implementation }); }, }); ``` **Multi-capability plugin** (provider + tool): ```typescript import { definePluginEntry } from "openclaw/plugin-sdk/core"; export default definePluginEntry({ id: "my-plugin", name: "My Plugin", register(api) { api.registerProvider({ /* ... */ }); api.registerTool({ /* ... */ }); api.registerImageGenerationProvider({ /* ... */ }); }, }); ``` Use `defineChannelPluginEntry` for channel plugins and `definePluginEntry` for everything else. A single plugin can register as many capabilities as needed. Always import from specific `openclaw/plugin-sdk/\` paths. The old monolithic import is deprecated (see [SDK Migration](/plugins/sdk-migration)). If older plugin code still imports `openclaw/extension-api`, treat that as a temporary compatibility bridge only. New code should use injected runtime helpers such as `api.runtime.agent.*` instead of importing host-side agent helpers directly. ```typescript // Correct: focused subpaths import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store"; import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-oauth"; // Wrong: monolithic root (lint will reject this) import { ... } from "openclaw/plugin-sdk"; // Deprecated: legacy host bridge import { runEmbeddedPiAgent } from "openclaw/extension-api"; ``` | Subpath | Purpose | | --- | --- | | `plugin-sdk/core` | Plugin entry definitions and base types | | `plugin-sdk/channel-setup` | Setup wizard adapters | | `plugin-sdk/channel-pairing` | DM pairing primitives | | `plugin-sdk/channel-reply-pipeline` | Reply prefix + typing wiring | | `plugin-sdk/channel-config-schema` | Config schema builders | | `plugin-sdk/channel-policy` | Group/DM policy helpers | | `plugin-sdk/secret-input` | Secret input parsing/helpers | | `plugin-sdk/webhook-ingress` | Webhook request/target helpers | | `plugin-sdk/runtime-store` | Persistent plugin storage | | `plugin-sdk/allow-from` | Allowlist resolution | | `plugin-sdk/reply-payload` | Message reply types | | `plugin-sdk/provider-oauth` | OAuth login + PKCE helpers | | `plugin-sdk/provider-onboard` | Provider onboarding config patches | | `plugin-sdk/testing` | Test utilities | Use the narrowest subpath that matches the job. Within your plugin, create local module files for internal code sharing instead of re-importing through the plugin SDK: ```typescript // api.ts — public exports for this plugin export { MyConfig } from "./src/config.js"; export { MyRuntime } from "./src/runtime.js"; // runtime-api.ts — internal-only exports export { internalHelper } from "./src/helpers.js"; ``` Never import your own plugin back through its published SDK path from production files. Route internal imports through local files like `./api.ts` or `./runtime-api.ts`. The SDK path is for external consumers only. Create `openclaw.plugin.json` in your plugin root: ```json { "id": "my-plugin", "kind": "provider", "name": "My Plugin", "description": "Adds My Provider to OpenClaw" } ``` For channel plugins, set `"kind": "channel"` and add `"channels": ["my-channel"]`. See [Plugin Manifest](/plugins/manifest) for the full schema. **External plugins:** run your own test suite against the plugin SDK contracts. **In-repo plugins:** OpenClaw runs contract tests against all registered plugins: ```bash pnpm test:contracts:channels # channel plugins pnpm test:contracts:plugins # provider plugins ``` For unit tests, import test helpers from the testing surface: ```typescript import { createTestRuntime } from "openclaw/plugin-sdk/testing"; ``` **External plugins:** publish to npm, then install: ```bash npm publish openclaw plugins install @myorg/openclaw-my-plugin ``` **In-repo plugins:** place the plugin under `extensions/` and it is automatically discovered during build. Users can browse and install community plugins with: ```bash openclaw plugins search openclaw plugins install ``` ## Registering agent tools Plugins can register **agent tools** — typed functions the LLM can call. Tools can be required (always available) or optional (users opt in via allowlists). ```typescript import { Type } from "@sinclair/typebox"; export default definePluginEntry({ id: "my-plugin", name: "My Plugin", 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 }, ); }, }); ``` Enable optional tools in config: ```json5 { tools: { allow: ["workflow_tool"] }, } ``` Tips: - Tool names must not clash with core tool names (conflicts are skipped) - Use `optional: true` for tools that trigger side effects or require extra binaries - Users can enable all tools from a plugin by adding the plugin id to `tools.allow` ## Lint enforcement (in-repo plugins) Three scripts enforce SDK boundaries for plugins in the OpenClaw repository: 1. **No monolithic root imports** — `openclaw/plugin-sdk` root is rejected 2. **No direct src/ imports** — plugins cannot import `../../src/` directly 3. **No self-imports** — plugins cannot import their own `plugin-sdk/\` subpath Run `pnpm check` to verify all boundaries before committing. External plugins are not subject to these lint rules, but following the same patterns is strongly recommended. ## Pre-submission checklist **package.json** has correct `openclaw` metadata Entry point uses `defineChannelPluginEntry` or `definePluginEntry` All imports use focused `plugin-sdk/\` paths Internal imports use local modules, not SDK self-imports `openclaw.plugin.json` manifest is present and valid Tests pass `pnpm check` passes (in-repo plugins) ## Related - [Plugin SDK Migration](/plugins/sdk-migration) — migrating from deprecated compat surfaces - [Plugin Architecture](/plugins/architecture) — internals and capability model - [Plugin Manifest](/plugins/manifest) — full manifest schema - [Plugin Agent Tools](/plugins/building-plugins#registering-agent-tools) — adding agent tools in a plugin - [Community Plugins](/plugins/community) — listing and quality bar