OpenClaw Plugins: Extend Your AI Agent
OpenClaw Plugins
Section titled “OpenClaw Plugins”Want a feature that’s not in core OpenClaw? Plugins are the answer. They’re small code modules that add commands, tools, channels, or even full integration flows—without touching the main codebase.
I use plugins for everything from voice calls to custom Slack integrations. Once you get the pattern, they’re surprisingly easy to build.
What You’ll Need
Section titled “What You’ll Need”- OpenClaw installed and running
- Basic TypeScript knowledge (for custom plugins)
Quick Start (3 minutes)
Section titled “Quick Start (3 minutes)”Step 1: See what’s already loaded
Section titled “Step 1: See what’s already loaded”openclaw plugins listStep 2: Install an official plugin
Section titled “Step 2: Install an official plugin”openclaw plugins install @openclaw/voice-callStep 3: Restart and configure
Section titled “Step 3: Restart and configure”Restart your Gateway, then add config under plugins.entries.<id>.config:
{ plugins: { entries: { "voice-call": { enabled: true, config: { provider: "twilio" } } } }}Done. Your new plugin is ready.
Official Plugins
Section titled “Official Plugins”| Plugin | Package | Description |
|---|---|---|
| Voice Call | @openclaw/voice-call | Make and receive phone calls |
| Microsoft Teams | @openclaw/msteams | Teams channel integration |
| Matrix | @openclaw/matrix | Matrix chat protocol |
| Nostr | @openclaw/nostr | Nostr decentralized chat |
| Zalo | @openclaw/zalo | Vietnamese messaging app |
Bundled plugins (disabled by default):
- Memory (Core) — basic memory search
- Memory (LanceDB) — long-term memory with auto-recall
- Google/Gemini/Qwen OAuth — provider authentication flows
Enable bundled plugins with:
openclaw plugins enable memory-lancedbPlugin Discovery
Section titled “Plugin Discovery”OpenClaw scans for plugins in this order:
- Config paths —
plugins.load.paths - Workspace extensions —
.openclaw/extensions/*.ts - Global extensions —
~/.openclaw/extensions/*.ts - Bundled — shipped with OpenClaw (disabled by default)
First match wins. Later copies are ignored.
Configuration
Section titled “Configuration”{ plugins: { enabled: true, allow: ["voice-call"], // allowlist (optional) deny: ["untrusted-plugin"], // denylist takes priority load: { paths: ["~/my-plugins/custom"] }, entries: { "voice-call": { enabled: true, config: { provider: "twilio" } } } }}Config changes require a Gateway restart.
Plugin Slots (Exclusive Categories)
Section titled “Plugin Slots (Exclusive Categories)”Some categories allow only one active plugin (like memory providers):
{ plugins: { slots: { memory: "memory-lancedb" // or "memory-core" or "none" } }}CLI Commands
Section titled “CLI Commands”openclaw plugins list # See all pluginsopenclaw plugins info <id> # Plugin detailsopenclaw plugins install <path|npm> # Install pluginopenclaw plugins install -l <path> # Link for developmentopenclaw plugins enable <id> # Enable pluginopenclaw plugins disable <id> # Disable pluginopenclaw plugins update <id> # Update npm pluginopenclaw plugins update --all # Update all npm pluginsopenclaw plugins doctor # Diagnose issuesBuilding Your Own Plugin
Section titled “Building Your Own Plugin”Minimal Plugin
Section titled “Minimal Plugin”Create ~/.openclaw/extensions/my-plugin/index.ts:
export default function(api) { api.registerGatewayMethod("myplugin.status", ({ respond }) => { respond(true, { status: "running" }); });}Create ~/.openclaw/extensions/my-plugin/openclaw.plugin.json:
{ "id": "my-plugin", "name": "My Custom Plugin", "version": "1.0.0"}Restart Gateway—your plugin is live.
Register a Tool
Section titled “Register a Tool”export default function(api) { api.registerTool({ name: "my_tool", description: "Does something useful", parameters: { type: "object", properties: { input: { type: "string" } } }, handler: async ({ input }) => { return { result: `Processed: ${input}` }; } });}Register a Slash Command
Section titled “Register a Slash Command”export default function(api) { api.registerCommand({ name: "mystatus", description: "Show plugin status", handler: (ctx) => ({ text: `Plugin running on ${ctx.channel}` }) });}This runs without invoking the AI—great for quick toggles and status checks.
Register a Channel
Section titled “Register a Channel”const plugin = { id: "acmechat", meta: { label: "AcmeChat", docsPath: "/channels/acmechat", blurb: "AcmeChat messaging." }, capabilities: { chatTypes: ["direct"] }, config: { listAccountIds: (cfg) => Object.keys(cfg.channels?.acmechat?.accounts ?? {}), resolveAccount: (cfg, id) => cfg.channels?.acmechat?.accounts?.[id ?? "default"] }, outbound: { deliveryMode: "direct", sendText: async ({ text }) => { // Send message return { ok: true }; } }};
export default function(api) { api.registerChannel({ plugin });}Plugin Hooks
Section titled “Plugin Hooks”Plugins can ship hooks and register them at runtime. This lets a plugin bundle event-driven automation without a separate hook pack install.
import { registerPluginHooksFromDir } from "openclaw/plugin-sdk";
export default function register(api) { registerPluginHooksFromDir(api, "./hooks");}Notes:
- Hook directories follow the normal hook structure (
HOOK.md+handler.ts) - Hook eligibility rules still apply (OS/bins/env/config requirements)
- Plugin-managed hooks show up in
openclaw hooks listwithplugin:<id> - Enable/disable the plugin to control its hooks
Runtime Helpers
Section titled “Runtime Helpers”Plugins can access selected core helpers via api.runtime. For example, telephony TTS:
const result = await api.runtime.tts.textToSpeechTelephony({ text: "Hello from OpenClaw", cfg: api.config,});Notes:
- Uses core
messages.ttsconfiguration (OpenAI or ElevenLabs) - Returns PCM audio buffer + sample rate
- Plugins must resample/encode for their providers
- Edge TTS is not supported for telephony
Provider Plugins (Model Auth)
Section titled “Provider Plugins (Model Auth)”Plugins can register model provider auth flows so users can run OAuth or API-key setup inside OpenClaw.
api.registerProvider({ id: "acme", label: "AcmeAI", auth: [ { id: "oauth", label: "OAuth", kind: "oauth", run: async (ctx) => { // Run OAuth flow and return auth profiles return { profiles: [ { profileId: "acme:default", credential: { type: "oauth", provider: "acme", access: "...", refresh: "...", expires: Date.now() + 3600 * 1000, }, }, ], defaultModel: "acme/opus-1", }; }, }, ],});Notes:
runreceives aProviderAuthContextwithprompter,runtime,openUrlhelpers- Return
configPatchwhen you need to add default models - Return
defaultModelso--set-defaultcan update agent defaults
Users authenticate with:
openclaw models auth login --provider acme --method oauthRegister Auto-Reply Commands
Section titled “Register Auto-Reply Commands”Plugins can register custom slash commands that execute without invoking the AI agent:
export default function(api) { api.registerCommand({ name: "mystatus", description: "Show plugin status", acceptsArgs: false, requireAuth: true, handler: (ctx) => ({ text: `Plugin running on ${ctx.channel}` }) });}Command Context
Section titled “Command Context”| Field | Description |
|---|---|
senderId | Sender’s ID |
channel | Channel where command was sent |
isAuthorizedSender | Whether sender is authorized |
args | Arguments (if acceptsArgs: true) |
commandBody | Full command text |
config | Current OpenClaw config |
Command Options
Section titled “Command Options”| Option | Description |
|---|---|
name | Command name (without /) |
description | Help text |
acceptsArgs | Whether to accept arguments (default: false) |
requireAuth | Require authorized sender (default: true) |
handler | Function returning { text: string } |
Notes:
- Plugin commands process before built-in commands and the AI agent
- Command names are case-insensitive
- Reserved commands (
help,status,reset) cannot be overridden
Register Background Services
Section titled “Register Background Services”export default function(api) { api.registerService({ id: "my-service", start: () => api.logger.info("ready"), stop: () => api.logger.info("bye"), });}Register CLI Commands
Section titled “Register CLI Commands”export default function(api) { api.registerCli(({ program }) => { program.command("mycmd").action(() => { console.log("Hello"); }); }, { commands: ["mycmd"] });}Plugin Manifest
Section titled “Plugin Manifest”Every plugin needs openclaw.plugin.json:
{ "id": "my-plugin", "name": "My Plugin", "version": "1.0.0", "configSchema": { "type": "object", "properties": { "apiKey": { "type": "string" } } }, "uiHints": { "apiKey": { "label": "API Key", "sensitive": true } }}Publishing to npm
Section titled “Publishing to npm”- Add to
package.json:
{ "name": "@yourscope/my-plugin", "openclaw": { "extensions": ["./index.ts"] }}-
Publish:
npm publish -
Users install with:
openclaw plugins install @yourscope/my-pluginTroubleshooting
Section titled “Troubleshooting”Plugin not loading
Section titled “Plugin not loading”Check:
- Is
plugins.enabledtrue? - Is the plugin in
denylist? - Does
openclaw.plugin.jsonexist?
Config validation errors
Section titled “Config validation errors”Cause: Unknown plugin IDs in config are strict errors.
Fix: Remove references to disabled/uninstalled plugins from entries, allow, deny.
Plugin conflicts
Section titled “Plugin conflicts”Cause: Multiple plugins with same ID.
Fix: Only first-discovered plugin loads. Remove duplicates from extension directories.
Still stuck? Our AI Setup Assistant can help debug your plugin setup.
Package Packs
Section titled “Package Packs”A plugin directory may include a package.json with openclaw.extensions:
{ "name": "my-pack", "openclaw": { "extensions": ["./src/safety.ts", "./src/tools.ts"] }}Each entry becomes a plugin. If the pack lists multiple extensions, the plugin id becomes name/<fileBase>.
If your plugin imports npm deps, install them in that directory:
cd ~/.openclaw/extensions/my-packnpm installChannel Catalog Metadata
Section titled “Channel Catalog Metadata”Channel plugins can advertise onboarding metadata via openclaw.channel and install hints via openclaw.install:
{ "name": "@openclaw/nextcloud-talk", "openclaw": { "extensions": ["./index.ts"], "channel": { "id": "nextcloud-talk", "label": "Nextcloud Talk", "selectionLabel": "Nextcloud Talk (self-hosted)", "docsPath": "/channels/nextcloud-talk", "blurb": "Self-hosted chat via Nextcloud Talk webhook bots.", "order": 65, "aliases": ["nc-talk", "nc"] }, "install": { "npmSpec": "@openclaw/nextcloud-talk", "localPath": "extensions/nextcloud-talk", "defaultChoice": "npm" } }}External catalogs: Drop JSON files at:
~/.openclaw/mpm/plugins.json~/.openclaw/mpm/catalog.json~/.openclaw/plugins/catalog.json
Or set OPENCLAW_PLUGIN_CATALOG_PATHS environment variable.
Writing a New Messaging Channel
Section titled “Writing a New Messaging Channel”Use this when you want a new chat surface (not a model provider).
Step 1: Pick an ID + Config Shape
Section titled “Step 1: Pick an ID + Config Shape”All channel config lives under channels.<id>:
{ channels: { acmechat: { accounts: { default: { token: "TOKEN", enabled: true } } } }}Step 2: Define Channel Metadata
Section titled “Step 2: Define Channel Metadata”| Field | Purpose |
|---|---|
meta.label | Display name in CLI/UI |
meta.selectionLabel | Longer selection text |
meta.docsPath | Link to docs (e.g., /channels/acmechat) |
meta.blurb | Short description |
meta.aliases | Alternative channel IDs |
meta.preferOver | Replace another channel |
Step 3: Implement Required Adapters
Section titled “Step 3: Implement Required Adapters”const plugin = { id: "acmechat", meta: { /* ... */ }, capabilities: { chatTypes: ["direct"] }, config: { listAccountIds: (cfg) => Object.keys(cfg.channels?.acmechat?.accounts ?? {}), resolveAccount: (cfg, id) => cfg.channels?.acmechat?.accounts?.[id ?? "default"] }, outbound: { deliveryMode: "direct", sendText: async ({ text }) => ({ ok: true }) }};Step 4: Add Optional Adapters
Section titled “Step 4: Add Optional Adapters”| Adapter | Purpose |
|---|---|
setup | Wizard integration |
security | DM policy |
status | Health/diagnostics |
gateway | Start/stop/login |
mentions | @mention handling |
threading | Thread support |
streaming | Streaming responses |
actions | Message actions |
commands | Native command behavior |
Step 5: Register
Section titled “Step 5: Register”export default function(api) { api.registerChannel({ plugin });}Naming Conventions
Section titled “Naming Conventions”| Type | Convention | Example |
|---|---|---|
| Gateway methods | pluginId.action | voicecall.status |
| Tools | snake_case | voice_call |
| CLI commands | kebab-case | voicecall-start |
Avoid clashing with core commands.
Skills in Plugins
Section titled “Skills in Plugins”Plugins can ship skills by adding a skills/ directory:
my-plugin/├── index.ts├── openclaw.plugin.json└── skills/ └── my-skill/ └── SKILL.mdEnable with plugins.entries.<id>.enabled and ensure it’s present in your managed skills locations.
Safety Notes
Section titled “Safety Notes”Plugins run in-process with the Gateway. Treat them as trusted code:
- Only install plugins you trust
- Prefer
plugins.allowallowlists - Restart the Gateway after changes
- Review plugin source before enabling
Testing Plugins
Section titled “Testing Plugins”Plugins should ship tests:
- In-repo plugins: Keep Vitest tests under
src/**(example:src/plugins/voice-call.plugin.test.ts) - Published plugins: Run own CI and validate
openclaw.extensionspoints at built entrypoint
# Run plugin testscd ~/.openclaw/extensions/my-pluginnpm testWhat’s Next?
Section titled “What’s Next?”- Plugin Agent Tools → — Build AI-callable tools
- Plugin Manifest → — Full manifest reference
- Voice Call Plugin → — Example plugin walkthrough
Need help? Join the OpenClaw Discord or check the GitHub Issues.
OpenClaw Expert
Still stuck?
If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.