2026-01-11 12:11:12 +00:00
---
2026-01-31 21:13:13 +09:00
summary: "OpenClaw plugins/extensions: discovery, config, and safety"
2026-01-11 12:11:12 +00:00
read_when:
- Adding or modifying plugins/extensions
- Documenting plugin install or load rules
2026-01-31 16:04:03 -05:00
title: "Plugins"
2026-01-11 12:11:12 +00:00
---
2026-01-31 18:31:49 +09:00
2026-01-11 12:11:12 +00:00
# Plugins (Extensions)
2026-01-12 01:27:05 +00:00
## Quick start (new to plugins?)
2026-01-30 03:15:10 +01:00
A plugin is just a **small code module ** that extends OpenClaw with extra
2026-01-12 01:27:05 +00:00
features (commands, tools, and Gateway RPC).
Most of the time, you’ ll use plugins when you want a feature that’ s not built
2026-01-30 03:15:10 +01:00
into core OpenClaw yet (or you want to keep optional features out of your main
2026-01-12 01:27:05 +00:00
install).
Fast path:
2026-01-31 18:31:49 +09:00
1. See what’ s already loaded:
2026-01-12 01:27:05 +00:00
``` bash
2026-01-30 03:15:10 +01:00
openclaw plugins list
2026-01-12 01:27:05 +00:00
```
2026-02-06 10:00:08 -05:00
2. Install an official plugin (example: Voice Call):
2026-01-12 01:27:05 +00:00
``` bash
2026-01-30 03:15:10 +01:00
openclaw plugins install @openclaw/voice-call
2026-01-12 01:27:05 +00:00
```
2026-03-06 11:13:30 -05:00
Npm specs are **registry-only ** (package name + optional **exact version ** or
**dist-tag ** ). Git/URL/file specs and semver ranges are rejected.
Bare specs and `@latest` stay on the stable track. If npm resolves either of
those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a
prerelease tag such as `@beta` /`@rc` or an exact prerelease version.
2026-02-14 14:07:07 +01:00
2026-02-06 10:00:08 -05:00
3. Restart the Gateway, then configure under `plugins.entries.<id>.config` .
2026-01-12 01:27:05 +00:00
See [Voice Call ](/plugins/voice-call ) for a concrete example plugin.
2026-02-17 17:42:37 +01:00
Looking for third-party listings? See [Community plugins ](/plugins/community ).
2026-01-12 01:27:05 +00:00
2026-01-15 09:07:14 +00:00
## Available plugins (official)
2026-01-30 03:15:10 +01:00
- Microsoft Teams is plugin-only as of 2026.1.15; install `@openclaw/msteams` if you use Teams.
2026-01-18 02:12:01 +00:00
- Memory (Core) — bundled memory search plugin (enabled by default via `plugins.slots.memory` )
2026-01-18 15:47:56 +00:00
- Memory (LanceDB) — bundled long-term memory plugin (auto-recall/capture; set `plugins.slots.memory = "memory-lancedb"` )
2026-01-30 03:15:10 +01:00
- [Voice Call ](/plugins/voice-call ) — `@openclaw/voice-call`
- [Zalo Personal ](/plugins/zalouser ) — `@openclaw/zalouser`
- [Matrix ](/channels/matrix ) — `@openclaw/matrix`
- [Nostr ](/channels/nostr ) — `@openclaw/nostr`
- [Zalo ](/channels/zalo ) — `@openclaw/zalo`
- [Microsoft Teams ](/channels/msteams ) — `@openclaw/msteams`
2026-01-17 09:33:56 +00:00
- Google Antigravity OAuth (provider auth) — bundled as `google-antigravity-auth` (disabled by default)
- Gemini CLI OAuth (provider auth) — bundled as `google-gemini-cli-auth` (disabled by default)
2026-01-17 20:28:15 +00:00
- Qwen OAuth (provider auth) — bundled as `qwen-portal-auth` (disabled by default)
2026-01-18 16:06:45 +00:00
- Copilot Proxy (provider auth) — local VS Code Copilot Proxy bridge; distinct from built-in `github-copilot` device login (bundled, disabled by default)
2026-01-15 09:07:14 +00:00
2026-01-30 03:15:10 +01:00
OpenClaw plugins are **TypeScript modules ** loaded at runtime via jiti. **Config
2026-01-19 21:13:51 -06:00
validation does not execute plugin code**; it uses the plugin manifest and JSON
Schema instead. See [Plugin manifest ](/plugins/manifest ).
Plugins can register:
2026-01-11 12:11:12 +00:00
- Gateway RPC methods
2026-03-05 17:05:21 -05:00
- Gateway HTTP routes
2026-01-11 12:11:12 +00:00
- Agent tools
- CLI commands
- Background services
2026-03-06 08:55:58 -05:00
- Context engines
2026-01-11 12:11:12 +00:00
- Optional config validation
2026-01-23 00:49:32 +00:00
- **Skills** (by listing `skills` directories in the plugin manifest)
2026-01-23 03:17:10 +00:00
- **Auto-reply commands** (execute without invoking the AI agent)
2026-01-11 12:11:12 +00:00
Plugins run **in‑ process ** with the Gateway, so treat them as trusted code.
2026-01-18 04:07:19 +00:00
Tool authoring guide: [Plugin agent tools ](/plugins/agent-tools ).
2026-01-11 12:11:12 +00:00
2026-01-25 09:29:50 +00:00
## Runtime helpers
Plugins can access selected core helpers via `api.runtime` . For telephony TTS:
``` ts
const result = await api . runtime . tts . textToSpeechTelephony ( {
2026-01-31 21:13:13 +09:00
text : "Hello from OpenClaw" ,
2026-01-25 09:29:50 +00:00
cfg : api.config ,
} ) ;
```
Notes:
2026-01-31 18:31:49 +09:00
2026-01-25 09:29:50 +00:00
- Uses core `messages.tts` configuration (OpenAI or ElevenLabs).
- Returns PCM audio buffer + sample rate. Plugins must resample/encode for providers.
- Edge TTS is not supported for telephony.
2026-03-02 21:45:29 +00:00
For STT/transcription, plugins can call:
``` ts
const { text } = await api . runtime . stt . transcribeAudioFile ( {
filePath : "/tmp/inbound-audio.ogg" ,
cfg : api.config ,
// Optional when MIME cannot be inferred reliably:
mime : "audio/ogg" ,
} ) ;
```
Notes:
- Uses core media-understanding audio configuration (`tools.media.audio` ) and provider fallback order.
- Returns `{ text: undefined }` when no transcription output is produced (for example skipped/unsupported input).
2026-03-05 17:05:21 -05:00
## Gateway HTTP routes
Plugins can expose HTTP endpoints with `api.registerHttpRoute(...)` .
``` ts
api . registerHttpRoute ( {
path : "/acme/webhook" ,
auth : "plugin" ,
match : "exact" ,
handler : async ( _req , res ) = > {
res . statusCode = 200 ;
res . end ( "ok" ) ;
return true ;
} ,
} ) ;
```
Route fields:
- `path` : route path under the gateway HTTP server.
- `auth` : required. Use `"gateway"` to require normal gateway auth, or `"plugin"` for plugin-managed auth/webhook verification.
- `match` : optional. `"exact"` (default) or `"prefix"` .
- `replaceExisting` : optional. Allows the same plugin to replace its own existing route registration.
- `handler` : return `true` when the route handled the request.
Notes:
- `api.registerHttpHandler(...)` is obsolete. Use `api.registerHttpRoute(...)` .
- Plugin routes must declare `auth` explicitly.
- Exact `path + match` conflicts are rejected unless `replaceExisting: true` , and one plugin cannot replace another plugin's route.
2026-03-07 19:54:53 +00:00
- Overlapping routes with different `auth` levels are rejected. Keep `exact` /`prefix` fallthrough chains on the same auth level only.
2026-03-05 17:05:21 -05:00
2026-03-03 22:07:03 -05:00
## Plugin SDK import paths
Use SDK subpaths instead of the monolithic `openclaw/plugin-sdk` import when
authoring plugins:
- `openclaw/plugin-sdk/core` for generic plugin APIs, provider auth types, and shared helpers.
2026-03-04 01:19:17 -05:00
- `openclaw/plugin-sdk/compat` for bundled/internal plugin code that needs broader shared runtime helpers than `core` .
2026-03-03 22:07:03 -05:00
- `openclaw/plugin-sdk/telegram` for Telegram channel plugins.
- `openclaw/plugin-sdk/discord` for Discord channel plugins.
- `openclaw/plugin-sdk/slack` for Slack channel plugins.
- `openclaw/plugin-sdk/signal` for Signal channel plugins.
- `openclaw/plugin-sdk/imessage` for iMessage channel plugins.
- `openclaw/plugin-sdk/whatsapp` for WhatsApp channel plugins.
- `openclaw/plugin-sdk/line` for LINE channel plugins.
2026-03-04 02:31:44 -05:00
- `openclaw/plugin-sdk/msteams` for the bundled Microsoft Teams plugin surface.
- Bundled extension-specific subpaths are also available:
`openclaw/plugin-sdk/acpx` , `openclaw/plugin-sdk/bluebubbles` ,
`openclaw/plugin-sdk/copilot-proxy` , `openclaw/plugin-sdk/device-pair` ,
`openclaw/plugin-sdk/diagnostics-otel` , `openclaw/plugin-sdk/diffs` ,
`openclaw/plugin-sdk/feishu` ,
`openclaw/plugin-sdk/google-gemini-cli-auth` , `openclaw/plugin-sdk/googlechat` ,
`openclaw/plugin-sdk/irc` , `openclaw/plugin-sdk/llm-task` ,
`openclaw/plugin-sdk/lobster` , `openclaw/plugin-sdk/matrix` ,
`openclaw/plugin-sdk/mattermost` , `openclaw/plugin-sdk/memory-core` ,
`openclaw/plugin-sdk/memory-lancedb` ,
`openclaw/plugin-sdk/minimax-portal-auth` ,
`openclaw/plugin-sdk/nextcloud-talk` , `openclaw/plugin-sdk/nostr` ,
`openclaw/plugin-sdk/open-prose` , `openclaw/plugin-sdk/phone-control` ,
`openclaw/plugin-sdk/qwen-portal-auth` , `openclaw/plugin-sdk/synology-chat` ,
`openclaw/plugin-sdk/talk-voice` , `openclaw/plugin-sdk/test-utils` ,
`openclaw/plugin-sdk/thread-ownership` , `openclaw/plugin-sdk/tlon` ,
`openclaw/plugin-sdk/twitch` , `openclaw/plugin-sdk/voice-call` ,
`openclaw/plugin-sdk/zalo` , and `openclaw/plugin-sdk/zalouser` .
2026-03-03 22:07:03 -05:00
Compatibility note:
- `openclaw/plugin-sdk` remains supported for existing external plugins.
2026-03-04 02:31:44 -05:00
- New and migrated bundled plugins should use channel or extension-specific
subpaths; use `core` for generic surfaces and `compat` only when broader
shared helpers are required.
2026-03-04 01:19:17 -05:00
2026-03-05 23:07:13 -06:00
## Read-only channel inspection
If your plugin registers a channel, prefer implementing
`plugin.config.inspectAccount(cfg, accountId)` alongside `resolveAccount(...)` .
Why:
- `resolveAccount(...)` is the runtime path. It is allowed to assume credentials
are fully materialized and can fail fast when required secrets are missing.
- Read-only command paths such as `openclaw status` , `openclaw status --all` ,
`openclaw channels status` , `openclaw channels resolve` , and doctor/config
repair flows should not need to materialize runtime credentials just to
describe configuration.
Recommended `inspectAccount(...)` behavior:
- Return descriptive account state only.
- Preserve `enabled` and `configured` .
- Include credential source/status fields when relevant, such as:
- `tokenSource` , `tokenStatus`
- `botTokenSource` , `botTokenStatus`
- `appTokenSource` , `appTokenStatus`
- `signingSecretSource` , `signingSecretStatus`
- You do not need to return raw token values just to report read-only
availability. Returning `tokenStatus: "available"` (and the matching source
field) is enough for status-style commands.
- Use `configured_unavailable` when a credential is configured via SecretRef but
unavailable in the current command path.
This lets read-only commands report “configured but unavailable in this command
path” instead of crashing or misreporting the account as not configured.
2026-03-04 01:19:17 -05:00
Performance note:
- Plugin discovery and manifest metadata use short in-process caches to reduce
bursty startup/reload work.
- Set `OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1` or
`OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1` to disable these caches.
- Tune cache windows with `OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS` and
`OPENCLAW_PLUGIN_MANIFEST_CACHE_MS` .
2026-03-03 22:07:03 -05:00
2026-01-11 12:11:12 +00:00
## Discovery & precedence
2026-01-30 03:15:10 +01:00
OpenClaw scans, in order:
2026-01-11 12:11:12 +00:00
2026-01-31 18:31:49 +09:00
1. Config paths
2026-01-17 09:33:56 +00:00
- `plugins.load.paths` (file or directory)
2026-01-11 12:11:12 +00:00
2026-02-06 10:00:08 -05:00
2. Workspace extensions
2026-01-31 18:31:49 +09:00
2026-01-30 03:15:10 +01:00
- `<workspace>/.openclaw/extensions/*.ts`
- `<workspace>/.openclaw/extensions/*/index.ts`
2026-01-11 12:11:12 +00:00
2026-02-06 10:00:08 -05:00
3. Global extensions
2026-01-31 18:31:49 +09:00
2026-01-30 03:15:10 +01:00
- `~/.openclaw/extensions/*.ts`
- `~/.openclaw/extensions/*/index.ts`
2026-01-17 09:33:56 +00:00
2026-03-04 02:31:44 -05:00
4. Bundled extensions (shipped with OpenClaw, mostly disabled by default)
2026-01-31 18:31:49 +09:00
2026-01-30 03:15:10 +01:00
- `<openclaw>/extensions/*`
2026-01-17 09:33:56 +00:00
2026-03-04 02:31:44 -05:00
Most bundled plugins must be enabled explicitly via
`plugins.entries.<id>.enabled` or `openclaw plugins enable <id>` .
Default-on bundled plugin exceptions:
- `device-pair`
- `phone-control`
- `talk-voice`
- active memory slot plugin (default slot: `memory-core` )
Installed plugins are enabled by default, but can be disabled the same way.
2026-01-17 09:33:56 +00:00
2026-02-19 15:13:34 +01:00
Hardening notes:
- If `plugins.allow` is empty and non-bundled plugins are discoverable, OpenClaw logs a startup warning with plugin ids and sources.
- Candidate paths are safety-checked before discovery admission. OpenClaw blocks candidates when:
- extension entry resolves outside plugin root (including symlink/path traversal escapes),
- plugin root/source path is world-writable,
- path ownership is suspicious for non-bundled plugins (POSIX owner is neither current uid nor root).
- Loaded non-bundled plugins without install/load-path provenance emit a warning so you can pin trust (`plugins.allow` ) or install tracking (`plugins.installs` ).
2026-01-30 03:15:10 +01:00
Each plugin must include a `openclaw.plugin.json` file in its root. If a path
2026-01-19 21:13:51 -06:00
points at a file, the plugin root is the file's directory and must contain the
manifest.
2026-01-17 09:33:56 +00:00
If multiple plugins resolve to the same id, the first match in the order above
wins and lower-precedence copies are ignored.
2026-01-11 12:11:12 +00:00
### Package packs
2026-01-30 03:15:10 +01:00
A plugin directory may include a `package.json` with `openclaw.extensions` :
2026-01-11 12:11:12 +00:00
``` json
{
"name" : "my-pack" ,
2026-01-30 03:15:10 +01:00
"openclaw" : {
2026-01-11 12:11:12 +00:00
"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 so
`node_modules` is available (`npm install` / `pnpm install` ).
2026-02-19 15:34:58 +01:00
Security guardrail: every `openclaw.extensions` entry must stay inside the plugin
directory after symlink resolution. Entries that escape the package directory are
rejected.
2026-02-14 14:07:07 +01:00
Security note: `openclaw plugins install` installs plugin dependencies with
`npm install --ignore-scripts` (no lifecycle scripts). Keep plugin dependency
trees "pure JS/TS" and avoid packages that require `postinstall` builds.
2026-01-20 11:11:42 +00:00
### Channel catalog metadata
2026-01-30 03:15:10 +01:00
Channel plugins can advertise onboarding metadata via `openclaw.channel` and
install hints via `openclaw.install` . This keeps the core catalog data-free.
2026-01-20 11:11:42 +00:00
Example:
``` json
{
2026-01-30 03:15:10 +01:00
"name" : "@openclaw/nextcloud-talk" ,
"openclaw" : {
2026-01-20 11:11:42 +00:00
"extensions" : [ "./index.ts" ] ,
"channel" : {
"id" : "nextcloud-talk" ,
"label" : "Nextcloud Talk" ,
"selectionLabel" : "Nextcloud Talk (self-hosted)" ,
"docsPath" : "/channels/nextcloud-talk" ,
"docsLabel" : "nextcloud-talk" ,
"blurb" : "Self-hosted chat via Nextcloud Talk webhook bots." ,
"order" : 65 ,
"aliases" : [ "nc-talk" , "nc" ]
} ,
"install" : {
2026-01-30 03:15:10 +01:00
"npmSpec" : "@openclaw/nextcloud-talk" ,
2026-01-20 11:11:42 +00:00
"localPath" : "extensions/nextcloud-talk" ,
"defaultChoice" : "npm"
}
}
}
```
2026-01-30 03:15:10 +01:00
OpenClaw can also merge **external channel catalogs ** (for example, an MPM
2026-01-24 00:17:58 +00:00
registry export). Drop a JSON file at one of:
2026-01-31 18:31:49 +09:00
2026-01-30 03:15:10 +01:00
- `~/.openclaw/mpm/plugins.json`
- `~/.openclaw/mpm/catalog.json`
- `~/.openclaw/plugins/catalog.json`
2026-01-24 00:17:58 +00:00
2026-01-30 03:15:10 +01:00
Or point `OPENCLAW_PLUGIN_CATALOG_PATHS` (or `OPENCLAW_MPM_CATALOG_PATHS` ) at
2026-01-24 00:17:58 +00:00
one or more JSON files (comma/semicolon/`PATH` -delimited). Each file should
2026-01-30 03:15:10 +01:00
contain `{ "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] }` .
2026-01-24 00:17:58 +00:00
2026-01-11 12:11:12 +00:00
## Plugin IDs
Default plugin ids:
- Package packs: `package.json` `name`
- Standalone file: file base name (`~/.../voice-call.ts` → `voice-call` )
2026-01-30 03:15:10 +01:00
If a plugin exports `id` , OpenClaw uses it but warns when it doesn’ t match the
2026-01-11 12:11:12 +00:00
configured id.
## Config
``` json5
{
plugins: {
enabled: true,
2026-01-31 21:13:13 +09:00
allow: ["voice-call"],
deny: ["untrusted-plugin"],
load: { paths: ["~/Projects/oss/voice-call-extension"] },
2026-01-11 12:11:12 +00:00
entries: {
2026-01-31 21:13:13 +09:00
"voice-call": { enabled: true, config: { provider: "twilio" } },
2026-01-31 18:31:49 +09:00
},
},
2026-01-11 12:11:12 +00:00
}
```
Fields:
2026-01-31 18:31:49 +09:00
2026-01-11 12:11:12 +00:00
- `enabled` : master toggle (default: true)
- `allow` : allowlist (optional)
- `deny` : denylist (optional; deny wins)
- `load.paths` : extra plugin files/dirs
2026-03-06 08:55:58 -05:00
- `slots` : exclusive slot selectors such as `memory` and `contextEngine`
2026-01-11 12:11:12 +00:00
- `entries.<id>` : per‑ plugin toggles + config
Config changes **require a gateway restart ** .
2026-01-19 21:13:51 -06:00
Validation rules (strict):
2026-01-31 18:31:49 +09:00
2026-01-19 21:13:51 -06:00
- Unknown plugin ids in `entries` , `allow` , `deny` , or `slots` are **errors ** .
- Unknown `channels.<id>` keys are **errors ** unless a plugin manifest declares
the channel id.
- Plugin config is validated using the JSON Schema embedded in
2026-01-30 03:15:10 +01:00
`openclaw.plugin.json` (`configSchema` ).
2026-01-19 21:13:51 -06:00
- If a plugin is disabled, its config is preserved and a **warning ** is emitted.
2026-01-18 02:12:01 +00:00
## Plugin slots (exclusive categories)
Some plugin categories are **exclusive ** (only one active at a time). Use
`plugins.slots` to select which plugin owns the slot:
``` json5
{
plugins: {
slots: {
2026-01-31 21:13:13 +09:00
memory: "memory-core", // or "none" to disable memory plugins
2026-03-06 08:52:56 -05:00
contextEngine: "legacy", // or a plugin id such as "lossless-claw"
2026-01-31 18:31:49 +09:00
},
},
2026-01-18 02:12:01 +00:00
}
```
2026-03-06 08:52:56 -05:00
Supported exclusive slots:
- `memory` : active memory plugin (`"none"` disables memory plugins)
- `contextEngine` : active context engine plugin (`"legacy"` is the built-in default)
If multiple plugins declare `kind: "memory"` or `kind: "context-engine"` , only
the selected plugin loads for that slot. Others are disabled with diagnostics.
### Context engine plugins
Context engine plugins own session context orchestration for ingest, assembly,
and compaction. Register them from your plugin with
`api.registerContextEngine(id, factory)` , then select the active engine with
`plugins.slots.contextEngine` .
Use this when your plugin needs to replace or extend the default context
pipeline rather than just add memory search or hooks.
2026-01-18 02:12:01 +00:00
2026-01-12 01:16:46 +00:00
## Control UI (schema + labels)
The Control UI uses `config.schema` (JSON Schema + `uiHints` ) to render better forms.
2026-01-30 03:15:10 +01:00
OpenClaw augments `uiHints` at runtime based on discovered plugins:
2026-01-12 01:16:46 +00:00
- Adds per-plugin labels for `plugins.entries.<id>` / `.enabled` / `.config`
- Merges optional plugin-provided config field hints under:
`plugins.entries.<id>.config.<field>`
If you want your plugin config fields to show good labels/placeholders (and mark secrets as sensitive),
2026-01-19 21:13:51 -06:00
provide `uiHints` alongside your JSON Schema in the plugin manifest.
2026-01-12 01:16:46 +00:00
Example:
2026-01-19 21:13:51 -06:00
``` json
{
"id" : "my-plugin" ,
"configSchema" : {
"type" : "object" ,
"additionalProperties" : false ,
"properties" : {
"apiKey" : { "type" : "string" } ,
"region" : { "type" : "string" }
}
2026-01-12 01:16:46 +00:00
} ,
2026-01-19 21:13:51 -06:00
"uiHints" : {
"apiKey" : { "label" : "API Key" , "sensitive" : true } ,
"region" : { "label" : "Region" , "placeholder" : "us-east-1" }
}
}
2026-01-12 01:16:46 +00:00
```
2026-01-11 12:11:12 +00:00
## CLI
``` bash
2026-01-30 03:15:10 +01:00
openclaw plugins list
openclaw plugins info <id>
openclaw plugins install <path> # copy a local file/dir into ~/.openclaw/extensions/<id>
openclaw plugins install ./extensions/voice-call # relative path ok
openclaw plugins install ./plugin.tgz # install from a local tarball
openclaw plugins install ./plugin.zip # install from a local zip
openclaw plugins install -l ./extensions/voice-call # link (no copy) for dev
openclaw plugins install @openclaw/voice-call # install from npm
2026-02-19 15:10:57 +01:00
openclaw plugins install @openclaw/voice-call --pin # store exact resolved name@version
2026-01-30 03:15:10 +01:00
openclaw plugins update <id>
openclaw plugins update --all
openclaw plugins enable <id>
openclaw plugins disable <id>
openclaw plugins doctor
2026-01-11 12:11:12 +00:00
```
2026-01-16 05:54:47 +00:00
`plugins update` only works for npm installs tracked under `plugins.installs` .
2026-02-19 15:10:57 +01:00
If stored integrity metadata changes between updates, OpenClaw warns and asks for confirmation (use global `--yes` to bypass prompts).
2026-01-16 05:54:47 +00:00
2026-01-30 03:15:10 +01:00
Plugins may also register their own top‑ level commands (example: `openclaw voicecall` ).
2026-01-11 12:11:12 +00:00
## Plugin API (overview)
Plugins export either:
- A function: `(api) => { ... }`
- An object: `{ id, name, configSchema, register(api) { ... } }`
2026-03-06 08:52:56 -05:00
Context engine plugins can also register a runtime-owned context manager:
``` ts
export default function ( api ) {
api . registerContextEngine ( "lossless-claw" , ( ) = > ( {
info : { id : "lossless-claw" , name : "Lossless Claw" , ownsCompaction : true } ,
async ingest() {
return { ingested : true } ;
} ,
async assemble ( { messages } ) {
return { messages , estimatedTokens : 0 } ;
} ,
async compact() {
return { ok : true , compacted : false } ;
} ,
} ) ) ;
}
```
Then enable it in config:
``` json5
{
plugins: {
slots: {
contextEngine: "lossless-claw",
},
},
}
```
2026-01-18 05:56:59 +00:00
## Plugin hooks
2026-02-22 08:45:15 +01:00
Plugins can register hooks at runtime. This lets a plugin bundle event-driven
automation without a separate hook pack install.
2026-01-18 05:56:59 +00:00
### Example
2026-02-22 08:45:15 +01:00
``` ts
2026-01-18 05:56:59 +00:00
export default function register ( api ) {
2026-02-22 08:45:15 +01:00
api . registerHook (
"command:new" ,
async ( ) = > {
// Hook logic here.
} ,
{
name : "my-plugin.command-new" ,
description : "Runs when /new is invoked" ,
} ,
) ;
2026-01-18 05:56:59 +00:00
}
```
Notes:
2026-01-31 18:31:49 +09:00
2026-02-22 08:45:15 +01:00
- Register hooks explicitly via `api.registerHook(...)` .
2026-01-18 05:56:59 +00:00
- Hook eligibility rules still apply (OS/bins/env/config requirements).
2026-01-30 03:15:10 +01:00
- Plugin-managed hooks show up in `openclaw hooks list` with `plugin:<id>` .
- You cannot enable/disable plugin-managed hooks via `openclaw hooks` ; enable/disable the plugin instead.
2026-01-18 05:56:59 +00:00
2026-03-06 02:06:59 +08:00
### Agent lifecycle hooks (`api.on`)
For typed runtime lifecycle hooks, use `api.on(...)` :
``` ts
export default function register ( api ) {
api . on (
"before_prompt_build" ,
( event , ctx ) = > {
return {
prependSystemContext : "Follow company style guide." ,
} ;
} ,
{ priority : 10 } ,
) ;
}
```
Important hooks for prompt construction:
- `before_model_resolve` : runs before session load (`messages` are not available). Use this to deterministically override `modelOverride` or `providerOverride` .
- `before_prompt_build` : runs after session load (`messages` are available). Use this to shape prompt input.
- `before_agent_start` : legacy compatibility hook. Prefer the two explicit hooks above.
2026-03-05 18:15:54 -05:00
Core-enforced hook policy:
- Operators can disable prompt mutation hooks per plugin via `plugins.entries.<id>.hooks.allowPromptInjection: false` .
- When disabled, OpenClaw blocks `before_prompt_build` and ignores prompt-mutating fields returned from legacy `before_agent_start` while preserving legacy `modelOverride` and `providerOverride` .
2026-03-06 02:06:59 +08:00
`before_prompt_build` result fields:
- `prependContext` : prepends text to the user prompt for this run. Best for turn-specific or dynamic content.
- `systemPrompt` : full system prompt override.
- `prependSystemContext` : prepends text to the current system prompt.
- `appendSystemContext` : appends text to the current system prompt.
Prompt build order in embedded runtime:
1. Apply `prependContext` to the user prompt.
2. Apply `systemPrompt` override when provided.
3. Apply `prependSystemContext + current system prompt + appendSystemContext` .
Merge and precedence notes:
- Hook handlers run by priority (higher first).
- For merged context fields, values are concatenated in execution order.
- `before_prompt_build` values are applied before legacy `before_agent_start` fallback values.
Migration guidance:
- Move static guidance from `prependContext` to `prependSystemContext` (or `appendSystemContext` ) so providers can cache stable system-prefix content.
- Keep `prependContext` for per-turn dynamic context that should stay tied to the user message.
2026-01-16 00:39:29 +00:00
## Provider plugins (model auth)
Plugins can register **model provider auth ** flows so users can run OAuth or
2026-01-30 03:15:10 +01:00
API-key setup inside OpenClaw (no external scripts needed).
2026-01-16 00:39:29 +00:00
Register a provider via `api.registerProvider(...)` . Each provider exposes one
or more auth methods (OAuth, API key, device code, etc.). These methods power:
2026-01-30 03:15:10 +01:00
- `openclaw models auth login --provider <id> [--method <id>]`
2026-01-16 00:39:29 +00:00
Example:
``` ts
api . registerProvider ( {
2026-01-31 21:13:13 +09:00
id : "acme" ,
label : "AcmeAI" ,
2026-01-16 00:39:29 +00:00
auth : [
{
2026-01-31 21:13:13 +09:00
id : "oauth" ,
label : "OAuth" ,
kind : "oauth" ,
2026-01-16 00:39:29 +00:00
run : async ( ctx ) = > {
// Run OAuth flow and return auth profiles.
return {
profiles : [
{
2026-01-31 21:13:13 +09:00
profileId : "acme:default" ,
2026-01-16 00:39:29 +00:00
credential : {
2026-01-31 21:13:13 +09:00
type : "oauth" ,
provider : "acme" ,
access : "..." ,
refresh : "..." ,
2026-01-16 00:39:29 +00:00
expires : Date.now ( ) + 3600 * 1000 ,
} ,
} ,
] ,
2026-01-31 21:13:13 +09:00
defaultModel : "acme/opus-1" ,
2026-01-16 00:39:29 +00:00
} ;
} ,
} ,
] ,
} ) ;
```
Notes:
2026-01-31 18:31:49 +09:00
2026-01-16 00:39:29 +00:00
- `run` receives a `ProviderAuthContext` with `prompter` , `runtime` ,
`openUrl` , and `oauth.createVpsAwareHandlers` helpers.
- Return `configPatch` when you need to add default models or provider config.
- Return `defaultModel` so `--set-default` can update agent defaults.
2026-01-15 02:42:41 +00:00
### Register a messaging channel
Plugins can register **channel plugins ** that behave like built‑ in channels
(WhatsApp, Telegram, etc.). Channel config lives under `channels.<id>` and is
validated by your channel plugin code.
``` ts
const myChannel = {
2026-01-31 21:13:13 +09:00
id : "acmechat" ,
2026-01-15 02:42:41 +00:00
meta : {
2026-01-31 21:13:13 +09:00
id : "acmechat" ,
label : "AcmeChat" ,
selectionLabel : "AcmeChat (API)" ,
docsPath : "/channels/acmechat" ,
blurb : "demo channel plugin." ,
aliases : [ "acme" ] ,
2026-01-15 02:42:41 +00:00
} ,
2026-01-31 21:13:13 +09:00
capabilities : { chatTypes : [ "direct" ] } ,
2026-01-15 02:42:41 +00:00
config : {
2026-01-31 21:13:13 +09:00
listAccountIds : ( cfg ) = > Object . keys ( cfg . channels ? . acmechat ? . accounts ? ? { } ) ,
2026-01-15 02:42:41 +00:00
resolveAccount : ( cfg , accountId ) = >
2026-01-31 21:13:13 +09:00
cfg . channels ? . acmechat ? . accounts ? . [ accountId ? ? "default" ] ? ? {
2026-01-31 18:31:49 +09:00
accountId ,
} ,
2026-01-15 02:42:41 +00:00
} ,
outbound : {
2026-01-31 21:13:13 +09:00
deliveryMode : "direct" ,
2026-01-15 02:42:41 +00:00
sendText : async ( ) = > ( { ok : true } ) ,
} ,
} ;
export default function ( api ) {
api . registerChannel ( { plugin : myChannel } ) ;
}
```
Notes:
2026-01-31 18:31:49 +09:00
2026-01-15 02:42:41 +00:00
- Put config under `channels.<id>` (not `plugins.entries` ).
- `meta.label` is used for labels in CLI/UI lists.
- `meta.aliases` adds alternate ids for normalization and CLI inputs.
2026-01-20 11:49:31 +00:00
- `meta.preferOver` lists channel ids to skip auto-enable when both are configured.
- `meta.detailLabel` and `meta.systemImage` let UIs show richer channel labels/icons.
2026-01-15 02:42:41 +00:00
2026-02-26 01:14:57 -05:00
### Channel onboarding hooks
Channel plugins can define optional onboarding hooks on `plugin.onboarding` :
- `configure(ctx)` is the baseline setup flow.
- `configureInteractive(ctx)` can fully own interactive setup for both configured and unconfigured states.
- `configureWhenConfigured(ctx)` can override behavior only for already configured channels.
Hook precedence in the wizard:
1. `configureInteractive` (if present)
2. `configureWhenConfigured` (only when channel status is already configured)
3. fallback to `configure`
Context details:
- `configureInteractive` and `configureWhenConfigured` receive:
- `configured` (`true` or `false` )
- `label` (user-facing channel name used by prompts)
- plus the shared config/runtime/prompter/options fields
- Returning `"skip"` leaves selection and account tracking unchanged.
- Returning `{ cfg, accountId? }` applies config updates and records account selection.
2026-01-15 02:50:03 +00:00
### Write a new messaging channel (step‑ by‑ step)
2026-02-14 14:07:07 +01:00
Use this when you want a **new chat surface ** (a "messaging channel"), not a model provider.
2026-01-15 02:50:03 +00:00
Model provider docs live under `/providers/*` .
2026-01-31 18:31:49 +09:00
1. Pick an id + config shape
2026-01-15 02:50:03 +00:00
- All channel config lives under `channels.<id>` .
- Prefer `channels.<id>.accounts.<accountId>` for multi‑ account setups.
2026-02-06 10:00:08 -05:00
2. Define the channel metadata
2026-01-31 18:31:49 +09:00
2026-01-15 02:50:03 +00:00
- `meta.label` , `meta.selectionLabel` , `meta.docsPath` , `meta.blurb` control CLI/UI lists.
- `meta.docsPath` should point at a docs page like `/channels/<id>` .
2026-01-20 11:49:31 +00:00
- `meta.preferOver` lets a plugin replace another channel (auto-enable prefers it).
- `meta.detailLabel` and `meta.systemImage` are used by UIs for detail text/icons.
2026-01-15 02:50:03 +00:00
2026-02-06 10:00:08 -05:00
3. Implement the required adapters
2026-01-31 18:31:49 +09:00
2026-01-15 02:50:03 +00:00
- `config.listAccountIds` + `config.resolveAccount`
- `capabilities` (chat types, media, threads, etc.)
- `outbound.deliveryMode` + `outbound.sendText` (for basic send)
2026-02-06 10:00:08 -05:00
4. Add optional adapters as needed
2026-01-31 18:31:49 +09:00
2026-01-15 02:50:03 +00:00
- `setup` (wizard), `security` (DM policy), `status` (health/diagnostics)
- `gateway` (start/stop/login), `mentions` , `threading` , `streaming`
- `actions` (message actions), `commands` (native command behavior)
2026-02-06 10:00:08 -05:00
5. Register the channel in your plugin
2026-01-31 18:31:49 +09:00
2026-01-15 02:50:03 +00:00
- `api.registerChannel({ plugin })`
Minimal config example:
``` json5
{
channels: {
acmechat: {
accounts: {
2026-01-31 21:13:13 +09:00
default: { token: "ACME_TOKEN", enabled: true },
2026-01-31 18:31:49 +09:00
},
},
},
2026-01-15 02:50:03 +00:00
}
```
Minimal channel plugin (outbound‑ only):
``` ts
const plugin = {
2026-01-31 21:13:13 +09:00
id : "acmechat" ,
2026-01-15 02:50:03 +00:00
meta : {
2026-01-31 21:13:13 +09:00
id : "acmechat" ,
label : "AcmeChat" ,
selectionLabel : "AcmeChat (API)" ,
docsPath : "/channels/acmechat" ,
blurb : "AcmeChat messaging channel." ,
aliases : [ "acme" ] ,
2026-01-15 02:50:03 +00:00
} ,
2026-01-31 21:13:13 +09:00
capabilities : { chatTypes : [ "direct" ] } ,
2026-01-15 02:50:03 +00:00
config : {
2026-01-31 21:13:13 +09:00
listAccountIds : ( cfg ) = > Object . keys ( cfg . channels ? . acmechat ? . accounts ? ? { } ) ,
2026-01-15 02:50:03 +00:00
resolveAccount : ( cfg , accountId ) = >
2026-01-31 21:13:13 +09:00
cfg . channels ? . acmechat ? . accounts ? . [ accountId ? ? "default" ] ? ? {
2026-01-31 18:31:49 +09:00
accountId ,
} ,
2026-01-15 02:50:03 +00:00
} ,
outbound : {
2026-01-31 21:13:13 +09:00
deliveryMode : "direct" ,
2026-01-15 02:50:03 +00:00
sendText : async ( { text } ) = > {
// deliver `text` to your channel here
return { ok : true } ;
} ,
} ,
} ;
export default function ( api ) {
api . registerChannel ( { plugin } ) ;
}
```
Load the plugin (extensions dir or `plugins.load.paths` ), restart the gateway,
then configure `channels.<id>` in your config.
2026-01-18 04:07:19 +00:00
### Agent tools
2026-01-11 12:11:12 +00:00
2026-01-18 04:07:19 +00:00
See the dedicated guide: [Plugin agent tools ](/plugins/agent-tools ).
2026-01-11 12:11:12 +00:00
### Register a gateway RPC method
``` ts
export default function ( api ) {
2026-01-31 21:13:13 +09:00
api . registerGatewayMethod ( "myplugin.status" , ( { respond } ) = > {
2026-01-11 12:11:12 +00:00
respond ( true , { ok : true } ) ;
} ) ;
}
```
### Register CLI commands
``` ts
export default function ( api ) {
2026-01-31 18:31:49 +09:00
api . registerCli (
( { program } ) = > {
2026-01-31 21:13:13 +09:00
program . command ( "mycmd" ) . action ( ( ) = > {
console . log ( "Hello" ) ;
2026-01-31 18:31:49 +09:00
} ) ;
} ,
2026-01-31 21:13:13 +09:00
{ commands : [ "mycmd" ] } ,
2026-01-31 18:31:49 +09:00
) ;
2026-01-11 12:11:12 +00:00
}
```
2026-01-23 03:17:10 +00:00
### Register auto-reply commands
Plugins can register custom slash commands that execute **without invoking the
AI agent**. This is useful for toggle commands, status checks, or quick actions
that don't need LLM processing.
``` ts
export default function ( api ) {
api . registerCommand ( {
2026-01-31 21:13:13 +09:00
name : "mystatus" ,
description : "Show plugin status" ,
2026-01-23 03:17:10 +00:00
handler : ( ctx ) = > ( {
text : ` Plugin is running! Channel: ${ ctx . channel } ` ,
} ) ,
} ) ;
}
```
Command handler context:
- `senderId` : The sender's ID (if available)
- `channel` : The channel where the command was sent
- `isAuthorizedSender` : Whether the sender is an authorized user
- `args` : Arguments passed after the command (if `acceptsArgs: true` )
- `commandBody` : The full command text
2026-01-30 03:15:10 +01:00
- `config` : The current OpenClaw config
2026-01-23 03:17:10 +00:00
Command options:
- `name` : Command name (without the leading `/` )
2026-03-07 21:59:12 +00:00
- `nativeNames` : Optional native-command aliases for slash/menu surfaces. Use `default` for all native providers, or provider-specific keys like `discord`
2026-01-23 03:17:10 +00:00
- `description` : Help text shown in command lists
2026-01-23 03:21:46 +00:00
- `acceptsArgs` : Whether the command accepts arguments (default: false). If false and arguments are provided, the command won't match and the message falls through to other handlers
- `requireAuth` : Whether to require authorized sender (default: true)
2026-01-23 03:17:10 +00:00
- `handler` : Function that returns `{ text: string }` (can be async)
Example with authorization and arguments:
``` ts
api . registerCommand ( {
2026-01-31 21:13:13 +09:00
name : "setmode" ,
description : "Set plugin mode" ,
2026-01-23 03:17:10 +00:00
acceptsArgs : true ,
requireAuth : true ,
handler : async ( ctx ) = > {
2026-01-31 21:13:13 +09:00
const mode = ctx . args ? . trim ( ) || "default" ;
2026-01-23 03:17:10 +00:00
await saveMode ( mode ) ;
return { text : ` Mode set to: ${ mode } ` } ;
} ,
} ) ;
```
Notes:
2026-01-31 18:31:49 +09:00
2026-01-23 03:17:10 +00:00
- Plugin commands are processed **before ** built-in commands and the AI agent
- Commands are registered globally and work across all channels
- Command names are case-insensitive (`/MyStatus` matches `/mystatus` )
2026-01-23 03:21:46 +00:00
- Command names must start with a letter and contain only letters, numbers, hyphens, and underscores
- Reserved command names (like `help` , `status` , `reset` , etc.) cannot be overridden by plugins
- Duplicate command registration across plugins will fail with a diagnostic error
2026-01-23 03:17:10 +00:00
2026-01-11 12:11:12 +00:00
### Register background services
``` ts
export default function ( api ) {
api . registerService ( {
2026-01-31 21:13:13 +09:00
id : "my-service" ,
start : ( ) = > api . logger . info ( "ready" ) ,
stop : ( ) = > api . logger . info ( "bye" ) ,
2026-01-11 12:11:12 +00:00
} ) ;
}
```
## Naming conventions
- Gateway methods: `pluginId.action` (example: `voicecall.status` )
- Tools: `snake_case` (example: `voice_call` )
- CLI commands: kebab or camel, but avoid clashing with core commands
## Skills
Plugins can ship a skill in the repo (`skills/<name>/SKILL.md` ).
Enable it with `plugins.entries.<id>.enabled` (or other config gates) and ensure
it’ s present in your workspace/managed skills locations.
2026-01-12 01:16:46 +00:00
## Distribution (npm)
Recommended packaging:
2026-01-30 03:15:10 +01:00
- Main package: `openclaw` (this repo)
- Plugins: separate npm packages under `@openclaw/*` (example: `@openclaw/voice-call` )
2026-01-12 01:16:46 +00:00
Publishing contract:
2026-01-30 03:15:10 +01:00
- Plugin `package.json` must include `openclaw.extensions` with one or more entry files.
2026-01-12 01:16:46 +00:00
- Entry files can be `.js` or `.ts` (jiti loads TS at runtime).
2026-01-30 03:15:10 +01:00
- `openclaw plugins install <npm-spec>` uses `npm pack` , extracts into `~/.openclaw/extensions/<id>/` , and enables it in config.
2026-01-12 01:16:46 +00:00
- Config key stability: scoped packages are normalized to the **unscoped ** id for `plugins.entries.*` .
2026-01-11 12:11:12 +00:00
## Example plugin: Voice Call
2026-01-11 23:23:14 +00:00
This repo includes a voice‑ call plugin (Twilio or log fallback):
2026-01-11 12:11:12 +00:00
- Source: `extensions/voice-call`
- Skill: `skills/voice-call`
2026-01-30 03:15:10 +01:00
- CLI: `openclaw voicecall start|status`
2026-01-11 12:11:12 +00:00
- Tool: `voice_call`
2026-01-11 23:23:14 +00:00
- RPC: `voicecall.start` , `voicecall.status`
- Config (twilio): `provider: "twilio"` + `twilio.accountSid/authToken/from` (optional `statusCallbackUrl` , `twimlUrl` )
- Config (dev): `provider: "log"` (no network)
2026-01-11 12:11:12 +00:00
2026-01-12 01:16:46 +00:00
See [Voice Call ](/plugins/voice-call ) and `extensions/voice-call/README.md` for setup and usage.
2026-01-11 12:11:12 +00:00
## Safety notes
Plugins run in-process with the Gateway. Treat them as trusted code:
- Only install plugins you trust.
- Prefer `plugins.allow` allowlists.
- Restart the Gateway after changes.
2026-01-12 01:16:46 +00:00
## Testing plugins
Plugins can (and should) ship tests:
- In-repo plugins can keep Vitest tests under `src/**` (example: `src/plugins/voice-call.plugin.test.ts` ).
2026-01-31 18:31:49 +09:00
- Separately published plugins should run their own CI (lint/build/test) and validate `openclaw.extensions` points at the built entrypoint (`dist/index.js` ).