Plugins (Extensions)
Quick start (new to plugins?)
A plugin is either:- a native OpenClaw plugin (
openclaw.plugin.json+ runtime module), or - a compatible bundle (
.codex-plugin/plugin.jsonor.claude-plugin/plugin.json)
openclaw plugins, but only native OpenClaw plugins execute
runtime code in-process.
Most of the time, you’ll use plugins when you want a feature that’s not built
into core OpenClaw yet (or you want to keep optional features out of your main
install).
Fast path:
- See what’s already loaded:
- Install an official plugin (example: Voice Call):
@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.
- Restart the Gateway, then configure under
plugins.entries.<id>.config.
~/.claude/plugins/known_marketplaces.json. You can also pass an explicit
marketplace source with --marketplace.
Architecture
OpenClaw’s plugin system has four layers:- Manifest + discovery
OpenClaw finds candidate plugins from configured paths, workspace roots,
global extension roots, and bundled extensions. Discovery reads native
openclaw.plugin.jsonmanifests plus supported bundle manifests first. - Enablement + validation Core decides whether a discovered plugin is enabled, disabled, blocked, or selected for an exclusive slot such as memory.
- Runtime loading Native OpenClaw plugins are loaded in-process via jiti and register capabilities into a central registry. Compatible bundles are normalized into registry records without importing runtime code.
- Surface consumption The rest of OpenClaw reads the registry to expose tools, channels, provider setup, hooks, HTTP routes, CLI commands, and services.
- discovery + config validation should work from manifest/schema metadata without executing plugin code
- native runtime behavior comes from the plugin module’s
register(api)path
Compatible bundles
OpenClaw also recognizes two compatible external bundle layouts:- Codex-style bundles:
.codex-plugin/plugin.json - Claude-style bundles:
.claude-plugin/plugin.jsonor the default Claude component layout without a manifest - Cursor-style bundles:
.cursor-plugin/plugin.json
format=bundle, with a subtype of
codex or claude in verbose/info output.
See Plugin bundles for the exact detection rules, mapping
behavior, and current support matrix.
Today, OpenClaw treats these as capability packs, not native runtime
plugins:
- supported now: bundled
skills - supported now: Claude
commands/markdown roots, mapped into the normal OpenClaw skill loader - supported now: Claude bundle
settings.jsondefaults for embedded Pi agent settings (with shell override keys sanitized) - supported now: Cursor
.cursor/commands/*.mdroots, mapped into the normal OpenClaw skill loader - supported now: Codex bundle hook directories that use the OpenClaw hook-pack
layout (
HOOK.md+handler.ts/handler.js) - detected but not wired yet: other declared bundle capabilities such as agents, Claude hook automation, Cursor rules/hooks/MCP metadata, MCP/app/LSP metadata, output styles
HOOK.md plus handler.ts/handler.js under the declared hook roots).
Vendor-specific shell/JSON hook runtimes, including Claude hooks.json, are
only detected today and are not executed directly.
Execution model
Native OpenClaw plugins run in-process with the Gateway. They are not sandboxed. A loaded native plugin has the same process-level trust boundary as core code. Implications:- a native plugin can register tools, network handlers, hooks, and services
- a native plugin bug can crash or destabilize the gateway
- a malicious native plugin is equivalent to arbitrary code execution inside the OpenClaw process
plugins.allowtrusts plugin ids, not source provenance.- A workspace plugin with the same id as a bundled plugin intentionally shadows the bundled copy when that workspace plugin is enabled/allowlisted.
- This is normal and useful for local development, patch testing, and hotfixes.
Available plugins (official)
- Microsoft Teams is plugin-only as of 2026.1.15; install
@openclaw/msteamsif you use Teams. - Memory (Core) — bundled memory search plugin (enabled by default via
plugins.slots.memory) - Memory (LanceDB) — bundled long-term memory plugin (auto-recall/capture; set
plugins.slots.memory = "memory-lancedb") - Voice Call —
@openclaw/voice-call - Zalo Personal —
@openclaw/zalouser - Matrix —
@openclaw/matrix - Nostr —
@openclaw/nostr - Zalo —
@openclaw/zalo - Microsoft Teams —
@openclaw/msteams - Anthropic provider runtime — bundled as
anthropic(enabled by default) - BytePlus provider catalog — bundled as
byteplus(enabled by default) - Cloudflare AI Gateway provider catalog — bundled as
cloudflare-ai-gateway(enabled by default) - Google web search + Gemini CLI OAuth — bundled as
google(web search auto-loads it; provider auth stays opt-in) - GitHub Copilot provider runtime — bundled as
github-copilot(enabled by default) - Hugging Face provider catalog — bundled as
huggingface(enabled by default) - Kilo Gateway provider runtime — bundled as
kilocode(enabled by default) - Kimi Coding provider catalog — bundled as
kimi-coding(enabled by default) - MiniMax provider catalog + usage + OAuth — bundled as
minimax(enabled by default; ownsminimaxandminimax-portal) - Mistral provider capabilities — bundled as
mistral(enabled by default) - Model Studio provider catalog — bundled as
modelstudio(enabled by default) - Moonshot provider runtime — bundled as
moonshot(enabled by default) - NVIDIA provider catalog — bundled as
nvidia(enabled by default) - OpenAI provider runtime — bundled as
openai(enabled by default; owns bothopenaiandopenai-codex) - OpenCode Go provider capabilities — bundled as
opencode-go(enabled by default) - OpenCode Zen provider capabilities — bundled as
opencode(enabled by default) - OpenRouter provider runtime — bundled as
openrouter(enabled by default) - Qianfan provider catalog — bundled as
qianfan(enabled by default) - Qwen OAuth (provider auth + catalog) — bundled as
qwen-portal-auth(enabled by default) - Synthetic provider catalog — bundled as
synthetic(enabled by default) - Together provider catalog — bundled as
together(enabled by default) - Venice provider catalog — bundled as
venice(enabled by default) - Vercel AI Gateway provider catalog — bundled as
vercel-ai-gateway(enabled by default) - Volcengine provider catalog — bundled as
volcengine(enabled by default) - Xiaomi provider catalog + usage — bundled as
xiaomi(enabled by default) - Z.AI provider runtime — bundled as
zai(enabled by default) - Copilot Proxy (provider auth) — local VS Code Copilot Proxy bridge; distinct from built-in
github-copilotdevice login (bundled, disabled by default)
- Gateway RPC methods
- Gateway HTTP routes
- Agent tools
- CLI commands
- Background services
- Context engines
- Provider auth flows and model catalogs
- Provider runtime hooks for dynamic model ids, transport normalization, capability metadata, stream wrapping, cache TTL policy, missing-auth hints, built-in model suppression, catalog augmentation, runtime auth exchange, and usage/billing auth + snapshot resolution
- Optional config validation
- Skills (by listing
skillsdirectories in the plugin manifest) - Auto-reply commands (execute without invoking the AI agent)
Provider runtime hooks
Provider plugins now have two layers:- manifest metadata:
providerAuthEnvVarsfor cheap env-auth lookup before runtime load, plusproviderAuthChoicesfor cheap onboarding/auth-choice labels and CLI flag metadata before runtime load - config-time hooks:
catalog/ legacydiscovery - runtime hooks:
resolveDynamicModel,prepareDynamicModel,normalizeResolvedModel,capabilities,prepareExtraParams,wrapStreamFn,formatApiKey,refreshOAuth,buildAuthDoctorHint,isCacheTtlEligible,buildMissingAuthMessage,suppressBuiltInModel,augmentModelCatalog,isBinaryThinking,supportsXHighThinking,resolveDefaultThinkingLevel,isModernModelRef,prepareRuntimeAuth,resolveUsageAuth,fetchUsageSnapshot
providerAuthEnvVars when the provider has env-based credentials
that generic auth/status/model-picker paths should see without loading plugin
runtime. Use manifest providerAuthChoices when onboarding/auth-choice CLI
surfaces should know the provider’s choice id, group labels, and simple
one-flag auth wiring without loading provider runtime. Keep provider runtime
envVars for operator-facing hints such as onboarding labels or OAuth
client-id/client-secret setup vars.
Hook order
For model/provider plugins, OpenClaw uses hooks in this rough order:catalogPublish provider config intomodels.providersduringmodels.jsongeneration.- built-in/discovered model lookup OpenClaw tries the normal registry/catalog path first.
resolveDynamicModelSync fallback for provider-owned model ids that are not in the local registry yet.prepareDynamicModelAsync warm-up only on async model resolution paths, thenresolveDynamicModelruns again.normalizeResolvedModelFinal rewrite before the embedded runner uses the resolved model.capabilitiesProvider-owned transcript/tooling metadata used by shared core logic.prepareExtraParamsProvider-owned request-param normalization before generic stream option wrappers.wrapStreamFnProvider-owned stream wrapper after generic wrappers are applied.formatApiKeyProvider-owned auth-profile formatter used when a stored auth profile needs to become the runtimeapiKeystring.refreshOAuthProvider-owned OAuth refresh override for custom refresh endpoints or refresh-failure policy.buildAuthDoctorHintProvider-owned repair hint appended when OAuth refresh fails.isCacheTtlEligibleProvider-owned prompt-cache policy for proxy/backhaul providers.buildMissingAuthMessageProvider-owned replacement for the generic missing-auth recovery message.suppressBuiltInModelProvider-owned stale upstream model suppression plus optional user-facing error hint.augmentModelCatalogProvider-owned synthetic/final catalog rows appended after discovery.isBinaryThinkingProvider-owned on/off reasoning toggle for binary-thinking providers.supportsXHighThinkingProvider-ownedxhighreasoning support for selected models.resolveDefaultThinkingLevelProvider-owned default/thinklevel for a specific model family.isModernModelRefProvider-owned modern-model matcher used by live profile filters and smoke selection.prepareRuntimeAuthExchanges a configured credential into the actual runtime token/key just before inference.resolveUsageAuthResolves usage/billing credentials for/usageand related status surfaces.fetchUsageSnapshotFetches and normalizes provider-specific usage/quota snapshots after auth is resolved.
Which hook to use
catalog: publish provider config and model catalogs intomodels.providersresolveDynamicModel: handle pass-through or forward-compat model ids that are not in the local registry yetprepareDynamicModel: async warm-up before retrying dynamic resolution (for example refresh provider metadata cache)normalizeResolvedModel: rewrite a resolved model’s transport/base URL/compat before inferencecapabilities: publish provider-family and transcript/tooling quirks without hardcoding provider ids in coreprepareExtraParams: set provider defaults or normalize provider-specific per-model params before generic stream wrappingwrapStreamFn: add provider-specific headers/payload/model compat patches while still using the normalpi-aiexecution pathformatApiKey: turn a stored auth profile into the runtimeapiKeystring without hardcoding provider token blobs in corerefreshOAuth: own OAuth refresh for providers that do not fit the sharedpi-airefreshersbuildAuthDoctorHint: append provider-owned auth repair guidance when refresh failsisCacheTtlEligible: decide whether provider/model pairs should use cache TTL metadatabuildMissingAuthMessage: replace the generic auth-store error with a provider-specific recovery hintsuppressBuiltInModel: hide stale upstream rows and optionally return a provider-owned error for direct resolution failuresaugmentModelCatalog: append synthetic/final catalog rows after discovery and config mergingisBinaryThinking: expose binary on/off reasoning UX without hardcoding provider ids in/thinksupportsXHighThinking: opt specific models into thexhighreasoning levelresolveDefaultThinkingLevel: keep provider/model default reasoning policy out of coreisModernModelRef: keep live/smoke model family inclusion rules with the providerprepareRuntimeAuth: exchange a configured credential into the actual short-lived runtime token/key used for requestsresolveUsageAuth: resolve provider-owned credentials for usage/billing endpoints without hardcoding token parsing in corefetchUsageSnapshot: own provider-specific usage endpoint fetch/parsing while core keeps summary fan-out and formatting
- provider owns a catalog or base URL defaults: use
catalog - provider accepts arbitrary upstream model ids: use
resolveDynamicModel - provider needs network metadata before resolving unknown ids: add
prepareDynamicModel - provider needs transport rewrites but still uses a core transport: use
normalizeResolvedModel - provider needs transcript/provider-family quirks: use
capabilities - provider needs default request params or per-provider param cleanup: use
prepareExtraParams - provider needs request headers/body/model compat wrappers without a custom transport: use
wrapStreamFn - provider stores extra metadata in auth profiles and needs a custom runtime token shape: use
formatApiKey - provider needs a custom OAuth refresh endpoint or refresh failure policy: use
refreshOAuth - provider needs provider-owned auth repair guidance after refresh failure: use
buildAuthDoctorHint - provider needs proxy-specific cache TTL gating: use
isCacheTtlEligible - provider needs a provider-specific missing-auth recovery hint: use
buildMissingAuthMessage - provider needs to hide stale upstream rows or replace them with a vendor hint: use
suppressBuiltInModel - provider needs synthetic forward-compat rows in
models listand pickers: useaugmentModelCatalog - provider exposes only binary thinking on/off: use
isBinaryThinking - provider wants
xhighon only a subset of models: usesupportsXHighThinking - provider owns default
/thinkpolicy for a model family: useresolveDefaultThinkingLevel - provider owns live/smoke preferred-model matching: use
isModernModelRef - provider needs a token exchange or short-lived request credential: use
prepareRuntimeAuth - provider needs custom usage/quota token parsing or a different usage credential: use
resolveUsageAuth - provider needs a provider-specific usage endpoint or payload parser: use
fetchUsageSnapshot
Provider Example
Built-in examples
- Anthropic uses
resolveDynamicModel,capabilities,buildAuthDoctorHint,resolveUsageAuth,fetchUsageSnapshot,isCacheTtlEligible,resolveDefaultThinkingLevel, andisModernModelRefbecause it owns Claude 4.6 forward-compat, provider-family hints, auth repair guidance, usage endpoint integration, prompt-cache eligibility, and Claude default/adaptive thinking policy. - OpenAI uses
resolveDynamicModel,normalizeResolvedModel, andcapabilitiesplusbuildMissingAuthMessage,suppressBuiltInModel,augmentModelCatalog,supportsXHighThinking, andisModernModelRefbecause it owns GPT-5.4 forward-compat, the direct OpenAIopenai-completions->openai-responsesnormalization, Codex-aware auth hints, Spark suppression, synthetic OpenAI list rows, and GPT-5 thinking / live-model policy. - OpenRouter uses
catalogplusresolveDynamicModelandprepareDynamicModelbecause the provider is pass-through and may expose new model ids before OpenClaw’s static catalog updates. - GitHub Copilot uses
catalog,auth,resolveDynamicModel, andcapabilitiesplusprepareRuntimeAuthandfetchUsageSnapshotbecause it needs provider-owned device login, model fallback behavior, Claude transcript quirks, a GitHub token -> Copilot token exchange, and a provider-owned usage endpoint. - OpenAI Codex uses
catalog,resolveDynamicModel,normalizeResolvedModel,refreshOAuth, andaugmentModelCatalogplusprepareExtraParams,resolveUsageAuth, andfetchUsageSnapshotbecause it still runs on core OpenAI transports but owns its transport/base URL normalization, OAuth refresh fallback policy, default transport choice, synthetic Codex catalog rows, and ChatGPT usage endpoint integration. - Google AI Studio and Gemini CLI OAuth use
resolveDynamicModelandisModernModelRefbecause they own Gemini 3.1 forward-compat fallback and modern-model matching; Gemini CLI OAuth also usesformatApiKey,resolveUsageAuth, andfetchUsageSnapshotfor token formatting, token parsing, and quota endpoint wiring. - OpenRouter uses
capabilities,wrapStreamFn, andisCacheTtlEligibleto keep provider-specific request headers, routing metadata, reasoning patches, and prompt-cache policy out of core. - Moonshot uses
catalogpluswrapStreamFnbecause it still uses the shared OpenAI transport but needs provider-owned thinking payload normalization. - Kilocode uses
catalog,capabilities,wrapStreamFn, andisCacheTtlEligiblebecause it needs provider-owned request headers, reasoning payload normalization, Gemini transcript hints, and Anthropic cache-TTL gating. - Z.AI uses
resolveDynamicModel,prepareExtraParams,wrapStreamFn,isCacheTtlEligible,isBinaryThinking,isModernModelRef,resolveUsageAuth, andfetchUsageSnapshotbecause it owns GLM-5 fallback,tool_streamdefaults, binary thinking UX, modern-model matching, and both usage auth + quota fetching. - Mistral, OpenCode Zen, and OpenCode Go use
capabilitiesonly to keep transcript/tooling quirks out of core. - Catalog-only bundled providers such as
byteplus,cloudflare-ai-gateway,huggingface,kimi-coding,modelstudio,nvidia,qianfan,synthetic,together,venice,vercel-ai-gateway, andvolcengineusecatalogonly. - Qwen portal uses
catalog,auth, andrefreshOAuth. - MiniMax and Xiaomi use
catalogplus usage hooks because their/usagebehavior is plugin-owned even though inference still runs through the shared transports.
Load pipeline
At startup, OpenClaw does roughly this:- discover candidate plugin roots
- read native or compatible bundle manifests and package metadata
- reject unsafe candidates
- normalize plugin config (
plugins.enabled,allow,deny,entries,slots,load.paths) - decide enablement for each candidate
- load enabled native modules via jiti
- call native
register(api)hooks and collect registrations into the plugin registry - expose the registry to commands/runtime surfaces
Manifest-first behavior
The manifest is the control-plane source of truth. OpenClaw uses it to:- identify the plugin
- discover declared channels/skills/config schema or bundle capabilities
- validate
plugins.entries.<id>.config - augment Control UI labels/placeholders
- show install/catalog metadata
What the loader caches
OpenClaw keeps short in-process caches for:- discovery results
- manifest registry data
- loaded plugin registries
Runtime helpers
Plugins can access selected core helpers viaapi.runtime. For telephony TTS:
- Uses core
messages.ttsconfiguration (OpenAI or ElevenLabs). - Returns PCM audio buffer + sample rate. Plugins must resample/encode for providers.
- Edge TTS is not supported for telephony.
- 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).
Gateway HTTP routes
Plugins can expose HTTP endpoints withapi.registerHttpRoute(...).
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: returntruewhen the route handled the request.
api.registerHttpHandler(...)is obsolete. Useapi.registerHttpRoute(...).- Plugin routes must declare
authexplicitly. - Exact
path + matchconflicts are rejected unlessreplaceExisting: true, and one plugin cannot replace another plugin’s route. - Overlapping routes with different
authlevels are rejected. Keepexact/prefixfallthrough chains on the same auth level only.
Plugin SDK import paths
Use SDK subpaths instead of the monolithicopenclaw/plugin-sdk import when
authoring plugins:
openclaw/plugin-sdk/corefor generic plugin APIs, provider auth types, and shared helpers such as routing/session utilities and logger-backed runtimes.openclaw/plugin-sdk/compatfor bundled/internal plugin code that needs broader shared runtime helpers thancore.openclaw/plugin-sdk/telegramfor Telegram channel plugin types and shared channel-facing helpers. Built-in Telegram implementation internals stay private to the bundled extension.openclaw/plugin-sdk/discordfor Discord channel plugin types and shared channel-facing helpers. Built-in Discord implementation internals stay private to the bundled extension.openclaw/plugin-sdk/slackfor Slack channel plugin types and shared channel-facing helpers. Built-in Slack implementation internals stay private to the bundled extension.openclaw/plugin-sdk/signalfor Signal channel plugin types and shared channel-facing helpers. Built-in Signal implementation internals stay private to the bundled extension.openclaw/plugin-sdk/imessagefor iMessage channel plugin types and shared channel-facing helpers. Built-in iMessage implementation internals stay private to the bundled extension.openclaw/plugin-sdk/whatsappfor WhatsApp channel plugin types and shared channel-facing helpers. Built-in WhatsApp implementation internals stay private to the bundled extension.openclaw/plugin-sdk/linefor LINE channel plugins.openclaw/plugin-sdk/msteamsfor 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/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, andopenclaw/plugin-sdk/zalouser.
Provider catalogs
Provider plugins can define model catalogs for inference withregisterProvider({ catalog: { run(...) { ... } } }).
catalog.run(...) returns the same shape OpenClaw writes into
models.providers:
{ provider }for one provider entry{ providers }for multiple provider entries
catalog when the plugin owns provider-specific model ids, base URL
defaults, or auth-gated model metadata.
catalog.order controls when a plugin’s catalog merges relative to OpenClaw’s
built-in implicit providers:
simple: plain API-key or env-driven providersprofile: providers that appear when auth profiles existpaired: providers that synthesize multiple related provider entrieslate: last pass, after other implicit providers
discoverystill works as a legacy alias- if both
cataloganddiscoveryare registered, OpenClaw usescatalog
openclaw/plugin-sdkremains supported for existing external plugins.- New and migrated bundled plugins should use channel or extension-specific
subpaths; use
corefor generic surfaces andcompatonly when broader shared helpers are required.
Read-only channel inspection
If your plugin registers a channel, prefer implementingplugin.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.
inspectAccount(...) behavior:
- Return descriptive account state only.
- Preserve
enabledandconfigured. - Include credential source/status fields when relevant, such as:
tokenSource,tokenStatusbotTokenSource,botTokenStatusappTokenSource,appTokenStatussigningSecretSource,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_unavailablewhen a credential is configured via SecretRef but unavailable in the current command path.
- Plugin discovery and manifest metadata use short in-process caches to reduce bursty startup/reload work.
- Set
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1orOPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1to disable these caches. - Tune cache windows with
OPENCLAW_PLUGIN_DISCOVERY_CACHE_MSandOPENCLAW_PLUGIN_MANIFEST_CACHE_MS.
Discovery & precedence
OpenClaw scans, in order:- Config paths
plugins.load.paths(file or directory)
- Workspace extensions
<workspace>/.openclaw/extensions/*.ts<workspace>/.openclaw/extensions/*/index.ts
- Global extensions
~/.openclaw/extensions/*.ts~/.openclaw/extensions/*/index.ts
- Bundled extensions (shipped with OpenClaw; mixed default-on/default-off)
<openclaw>/extensions/*
plugins.entries.<id>.enabled or
openclaw plugins enable <id>.
Default-on bundled plugin examples:
bytepluscloudflare-ai-gatewaydevice-pairgithub-copilothuggingfacekilocodekimi-codingminimaxminimaxmodelstudiomoonshotnvidiaollamaopenaiopenrouterphone-controlqianfanqwen-portal-authsglangsynthetictalk-voicetogethervenicevercel-ai-gatewayvllmvolcenginexiaomi- active memory slot plugin (default slot:
memory-core)
- If
plugins.allowis 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).
openclaw.plugin.json file in its
root. If a path points at a file, the plugin root is the file’s directory and
must contain the manifest.
Compatible bundles may instead provide one of:
.codex-plugin/plugin.json.claude-plugin/plugin.json
- workspace plugins intentionally shadow bundled plugins with the same id
plugins.allow: ["foo"]authorizes the activefooplugin by id, even when the active copy comes from the workspace instead of the bundled extension root- if you need stricter provenance control, use explicit install/load paths and inspect the resolved plugin source before enabling it
Enablement rules
Enablement is resolved after discovery:plugins.enabled: falsedisables all pluginsplugins.denyalways winsplugins.entries.<id>.enabled: falsedisables that plugin- workspace-origin plugins are disabled by default
- allowlists restrict the active set when
plugins.allowis non-empty - allowlists are id-based, not source-based
- bundled plugins are disabled by default unless:
- the bundled id is in the built-in default-on set, or
- you explicitly enable it, or
- channel config implicitly enables the bundled channel plugin
- exclusive slots can force-enable the selected plugin for that slot
Package packs
A plugin directory may include apackage.json with openclaw.extensions:
name/<fileBase>.
If your plugin imports npm deps, install them in that directory so
node_modules is available (npm install / pnpm install).
Security guardrail: every openclaw.extensions entry must stay inside the plugin
directory after symlink resolution. Entries that escape the package directory are
rejected.
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.
Optional: openclaw.setupEntry can point at a lightweight setup-only module.
When OpenClaw needs setup surfaces for a disabled channel plugin, or
when a channel plugin is enabled but still unconfigured, it loads setupEntry
instead of the full plugin entry. This keeps startup and setup lighter
when your main plugin entry also wires tools, hooks, or other runtime-only
code.
Optional: openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen
can opt a channel plugin into the same setupEntry path during the gateway’s
pre-listen startup phase, even when the channel is already configured.
Use this only when setupEntry fully covers the startup surface that must exist
before the gateway starts listening. In practice, that means the setup entry
must register every channel-owned capability that startup depends on, such as:
- channel registration itself
- any HTTP routes that must be available before the gateway starts listening
- any gateway methods, tools, or services that must exist during that same window
Channel catalog metadata
Channel plugins can advertise setup/discovery metadata viaopenclaw.channel and
install hints via openclaw.install. This keeps the core catalog data-free.
Example:
~/.openclaw/mpm/plugins.json~/.openclaw/mpm/catalog.json~/.openclaw/plugins/catalog.json
OPENCLAW_PLUGIN_CATALOG_PATHS (or OPENCLAW_MPM_CATALOG_PATHS) at
one or more JSON files (comma/semicolon/PATH-delimited). Each file should
contain { "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] }.
Plugin IDs
Default plugin ids:- Package packs:
package.jsonname - Standalone file: file base name (
~/.../voice-call.ts→voice-call)
id, OpenClaw uses it but warns when it doesn’t match the
configured id.
Registry model
Loaded plugins do not directly mutate random core globals. They register into a central plugin registry. The registry tracks:- plugin records (identity, source, origin, status, diagnostics)
- tools
- legacy hooks and typed hooks
- channels
- providers
- gateway RPC handlers
- HTTP routes
- CLI registrars
- background services
- plugin-owned commands
- plugin module -> registry registration
- core runtime -> registry consumption
Config
enabled: master toggle (default: true)allow: allowlist (optional)deny: denylist (optional; deny wins)load.paths: extra plugin files/dirsslots: exclusive slot selectors such asmemoryandcontextEngineentries.<id>: per‑plugin toggles + config
- Unknown plugin ids in
entries,allow,deny, orslotsare errors. - Unknown
channels.<id>keys are errors unless a plugin manifest declares the channel id. - Native plugin config is validated using the JSON Schema embedded in
openclaw.plugin.json(configSchema). - Compatible bundles currently do not expose native OpenClaw config schemas.
- If a plugin is disabled, its config is preserved and a warning is emitted.
Disabled vs missing vs invalid
These states are intentionally different:- disabled: plugin exists, but enablement rules turned it off
- missing: config references a plugin id that discovery did not find
- invalid: plugin exists, but its config does not match the declared schema
Plugin slots (exclusive categories)
Some plugin categories are exclusive (only one active at a time). Useplugins.slots to select which plugin owns the slot:
memory: active memory plugin ("none"disables memory plugins)contextEngine: active context engine plugin ("legacy"is the built-in default)
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 withapi.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.
Control UI (schema + labels)
The Control UI usesconfig.schema (JSON Schema + uiHints) to render better forms.
OpenClaw augments uiHints at runtime based on discovered plugins:
- Adds per-plugin labels for
plugins.entries.<id>/.enabled/.config - Merges optional plugin-provided config field hints under:
plugins.entries.<id>.config.<field>
uiHints alongside your JSON Schema in the plugin manifest.
Example:
CLI
openclaw plugins list shows the top-level format as openclaw or bundle.
Verbose list/info output also shows bundle subtype (codex or claude) plus
detected bundle capabilities.
plugins update only works for npm installs tracked under plugins.installs.
If stored integrity metadata changes between updates, OpenClaw warns and asks for confirmation (use global --yes to bypass prompts).
Plugins may also register their own top‑level commands (example: openclaw voicecall).
Plugin API (overview)
Plugins export either:- A function:
(api) => { ... } - An object:
{ id, name, configSchema, register(api) { ... } }
register(api) is where plugins attach behavior. Common registrations include:
registerToolregisterHookon(...)for typed lifecycle hooksregisterChannelregisterProviderregisterHttpRouteregisterCommandregisterCliregisterContextEngineregisterService
Plugin hooks
Plugins can register hooks at runtime. This lets a plugin bundle event-driven automation without a separate hook pack install.Example
- Register hooks explicitly via
api.registerHook(...). - Hook eligibility rules still apply (OS/bins/env/config requirements).
- Plugin-managed hooks show up in
openclaw hooks listwithplugin:<id>. - You cannot enable/disable plugin-managed hooks via
openclaw hooks; enable/disable the plugin instead.
Agent lifecycle hooks (api.on)
For typed runtime lifecycle hooks, use api.on(...):
before_model_resolve: runs before session load (messagesare not available). Use this to deterministically overridemodelOverrideorproviderOverride.before_prompt_build: runs after session load (messagesare available). Use this to shape prompt input.before_agent_start: legacy compatibility hook. Prefer the two explicit hooks above.
- Operators can disable prompt mutation hooks per plugin via
plugins.entries.<id>.hooks.allowPromptInjection: false. - When disabled, OpenClaw blocks
before_prompt_buildand ignores prompt-mutating fields returned from legacybefore_agent_startwhile preserving legacymodelOverrideandproviderOverride.
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.
- Apply
prependContextto the user prompt. - Apply
systemPromptoverride when provided. - Apply
prependSystemContext + current system prompt + appendSystemContext.
- Hook handlers run by priority (higher first).
- For merged context fields, values are concatenated in execution order.
before_prompt_buildvalues are applied before legacybefore_agent_startfallback values.
- Move static guidance from
prependContexttoprependSystemContext(orappendSystemContext) so providers can cache stable system-prefix content. - Keep
prependContextfor per-turn dynamic context that should stay tied to the user message.
Provider plugins (model auth)
Plugins can register model providers so users can run OAuth or API-key setup inside OpenClaw, surface provider setup in onboarding/model-pickers, and contribute implicit provider discovery. Provider plugins are the modular extension seam for model-provider setup. They are not just “OAuth helpers” anymore.Provider plugin lifecycle
A provider plugin can participate in five distinct phases:- Auth
auth[].run(ctx)performs OAuth, API-key capture, device code, or custom setup and returns auth profiles plus optional config patches. - Non-interactive setup
auth[].runNonInteractive(ctx)handlesopenclaw onboard --non-interactivewithout prompts. Use this when the provider needs custom headless setup beyond the built-in simple API-key paths. - Wizard integration
wizard.setupadds an entry toopenclaw onboard.wizard.modelPickeradds a setup entry to the model picker. - Implicit discovery
discovery.run(ctx)can contribute provider config automatically during model resolution/listing. - Post-selection follow-up
onModelSelected(ctx)runs after a model is chosen. Use this for provider- specific work such as downloading a local model.
- auth is interactive and writes credentials/config
- non-interactive setup is flag/env-driven and must not prompt
- wizard metadata is static and UI-facing
- discovery should be safe, quick, and failure-tolerant
- post-select hooks are side effects tied to the chosen model
Provider auth contract
auth[].run(ctx) returns:
profiles: auth profiles to writeconfigPatch: optionalopenclaw.jsonchangesdefaultModel: optionalprovider/modelrefnotes: optional user-facing notes
- writes the returned auth profiles
- applies auth-profile config wiring
- merges the config patch
- optionally applies the default model
- runs the provider’s
onModelSelectedhook when appropriate
Provider non-interactive contract
auth[].runNonInteractive(ctx) is optional. Implement it when the provider
needs headless setup that cannot be expressed through the built-in generic
API-key flows.
The non-interactive context includes:
- the current and base config
- parsed onboarding CLI options
- runtime logging/error helpers
- agent/workspace dirs so the provider can persist auth into the same scoped store used by the rest of onboarding
resolveApiKey(...)to read provider keys from flags, env, or existing auth profiles while honoring--secret-input-modetoApiKeyCredential(...)to convert a resolved key into an auth-profile credential with the right plaintext vs secret-ref storage
- self-hosted OpenAI-compatible runtimes that need
--custom-base-url+--custom-model-id - provider-specific non-interactive verification or config synthesis
runNonInteractive. Reject missing inputs with actionable
errors instead.
Provider wizard metadata
Provider auth/onboarding metadata can live in two layers:- manifest
providerAuthChoices: cheap labels, grouping,--auth-choiceids, and simple CLI flag metadata available before runtime load - runtime
wizard.setup/auth[].wizard: richer behavior that depends on loaded provider code
wizard.setup controls how the provider appears in grouped onboarding:
choiceId: auth-choice valuechoiceLabel: option labelchoiceHint: short hintgroupId: group bucket idgroupLabel: group labelgroupHint: group hintmethodId: auth method to runmodelAllowlist: optional post-auth allowlist policy (allowedKeys,initialSelections,message)
wizard.modelPicker controls how a provider appears as a “set this up now”
entry in model selection:
labelhintmethodId
- duplicate or blank auth-method ids are rejected
- wizard metadata is ignored when the provider has no auth methods
- invalid
methodIdbindings are downgraded to warnings and fall back to the provider’s remaining auth methods
Provider discovery contract
discovery.run(ctx) returns one of:
{ provider }{ providers }null
{ provider } for the common case where the plugin owns one provider id.
Use { providers } when a plugin discovers multiple provider entries.
The discovery context includes:
- the current config
- agent/workspace dirs
- process env
- a helper to resolve the provider API key and a discovery-safe API key value
- fast
- best-effort
- safe to skip on failure
- careful about side effects
Discovery ordering
Provider discovery runs in ordered phases:simpleprofilepairedlate
simplefor cheap environment-only discoveryprofilewhen discovery depends on auth profilespairedfor providers that need to coordinate with another discovery steplatefor expensive or local-network probing
late.
Good provider-plugin boundaries
Good fit for provider plugins:- local/self-hosted providers with custom setup flows
- provider-specific OAuth/device-code login
- implicit discovery of local model servers
- post-selection side effects such as model pulls
- trivial API-key-only providers that differ only by env var, base URL, and one default model
api.registerProvider(...). Each provider exposes one
or more auth methods (OAuth, API key, device code, etc.). Those methods can
power:
openclaw models auth login --provider <id> [--method <id>]openclaw onboard- model-picker “custom provider” setup entries
- implicit provider discovery during model resolution/listing
runreceives aProviderAuthContextwithprompter,runtime,openUrl,oauth.createVpsAwareHandlers,secretInputMode, andallowSecretRefPrompthelpers/state. Onboarding/configure flows can use these to honor--secret-input-modeor offer env/file/exec secret-ref capture, whileopenclaw models authkeeps a tighter prompt surface.runNonInteractivereceives aProviderAuthMethodNonInteractiveContextwithopts,agentDir,resolveApiKey, andtoApiKeyCredentialhelpers for headless onboarding.- Return
configPatchwhen you need to add default models or provider config. - Return
defaultModelso--set-defaultcan update agent defaults. wizard.setupadds a provider choice to onboarding surfaces such asopenclaw onboard/openclaw setup --wizard.wizard.setup.modelAllowlistlets the provider narrow the follow-up model allowlist prompt during onboarding/configure.wizard.modelPickeradds a “setup this provider” entry to the model picker.deprecatedProfileIdslets the provider ownopenclaw doctorcleanup for retired auth-profile ids.discovery.runreturns either{ provider }for the plugin’s own provider id or{ providers }for multi-provider discovery.discovery.ordercontrols when the provider runs relative to built-in discovery phases:simple,profile,paired, orlate.onModelSelectedis the post-selection hook for provider-specific follow-up work such as pulling a local model.
Register a messaging channel
Plugins can register channel plugins that behave like built‑in channels (WhatsApp, Telegram, etc.). Channel config lives underchannels.<id> and is
validated by your channel plugin code.
- Put config under
channels.<id>(notplugins.entries). meta.labelis used for labels in CLI/UI lists.meta.aliasesadds alternate ids for normalization and CLI inputs.meta.preferOverlists channel ids to skip auto-enable when both are configured.meta.detailLabelandmeta.systemImagelet UIs show richer channel labels/icons.
Channel setup hooks
Preferred setup split:plugin.setupowns account-id normalization, validation, and config writes.plugin.setupWizardlets the host run the common wizard flow while the channel only supplies status, credential, DM allowlist, and channel-access descriptors.
plugin.setupWizard is best for channels that fit the shared pattern:
- one account picker driven by
plugin.config.listAccountIds - optional preflight/prepare step before prompting (for example installer/bootstrap work)
- optional env-shortcut prompt for bundled credential sets (for example paired bot/app tokens)
- one or more credential prompts, with each step either writing through
plugin.setup.applyAccountConfigor a channel-owned partial patch - optional non-secret text prompts (for example CLI paths, base URLs, account ids)
- optional channel/group access allowlist prompts resolved by the host
- optional DM allowlist resolution (for example
@username-> numeric id) - optional completion note after setup finishes
Write a new messaging channel (step‑by‑step)
Use this when you want a new chat surface (a “messaging channel”), not a model provider. Model provider docs live under/providers/*.
- Pick an id + config shape
- All channel config lives under
channels.<id>. - Prefer
channels.<id>.accounts.<accountId>for multi‑account setups.
- Define the channel metadata
meta.label,meta.selectionLabel,meta.docsPath,meta.blurbcontrol CLI/UI lists.meta.docsPathshould point at a docs page like/channels/<id>.meta.preferOverlets a plugin replace another channel (auto-enable prefers it).meta.detailLabelandmeta.systemImageare used by UIs for detail text/icons.
- Implement the required adapters
config.listAccountIds+config.resolveAccountcapabilities(chat types, media, threads, etc.)outbound.deliveryMode+outbound.sendText(for basic send)
- Add optional adapters as needed
setup(validation + config writes),setupWizard(host-owned wizard),security(DM policy),status(health/diagnostics)gateway(start/stop/login),mentions,threading,streamingactions(message actions),commands(native command behavior)
- Register the channel in your plugin
api.registerChannel({ plugin })
plugins.load.paths), restart the gateway,
then configure channels.<id> in your config.
Agent tools
See the dedicated guide: Plugin agent tools.Register a gateway RPC method
Register CLI commands
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.senderId: The sender’s ID (if available)channel: The channel where the command was sentisAuthorizedSender: Whether the sender is an authorized userargs: Arguments passed after the command (ifacceptsArgs: true)commandBody: The full command textconfig: The current OpenClaw config
name: Command name (without the leading/)nativeNames: Optional native-command aliases for slash/menu surfaces. Usedefaultfor all native providers, or provider-specific keys likediscorddescription: Help text shown in command listsacceptsArgs: 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 handlersrequireAuth: Whether to require authorized sender (default: true)handler: Function that returns{ text: string }(can be async)
- 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 (
/MyStatusmatches/mystatus) - 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
Register background services
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.
Distribution (npm)
Recommended packaging:- Main package:
openclaw(this repo) - Plugins: separate npm packages under
@openclaw/*(example:@openclaw/voice-call)
- Plugin
package.jsonmust includeopenclaw.extensionswith one or more entry files. - Optional:
openclaw.setupEntrymay point at a lightweight setup-only entry for disabled or still-unconfigured channel setup. - Optional:
openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListenmay opt a channel plugin into usingsetupEntryduring pre-listen gateway startup, but only when that setup entry completely covers the plugin’s startup-critical surface. - Entry files can be
.jsor.ts(jiti loads TS at runtime). openclaw plugins install <npm-spec>usesnpm pack, extracts into~/.openclaw/extensions/<id>/, and enables it in config.- Config key stability: scoped packages are normalized to the unscoped id for
plugins.entries.*.
Example plugin: Voice Call
This repo includes a voice‑call plugin (Twilio or log fallback):- Source:
extensions/voice-call - Skill:
skills/voice-call - CLI:
openclaw voicecall start|status - Tool:
voice_call - RPC:
voicecall.start,voicecall.status - Config (twilio):
provider: "twilio"+twilio.accountSid/authToken/from(optionalstatusCallbackUrl,twimlUrl) - Config (dev):
provider: "log"(no network)
extensions/voice-call/README.md for setup and usage.
Safety notes
Plugins run in-process with the Gateway. Treat them as trusted code:- Only install plugins you trust.
- Prefer
plugins.allowallowlists. - Remember that
plugins.allowis id-based, so an enabled workspace plugin can intentionally shadow a bundled plugin with the same id. - Restart the Gateway after changes.
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). - Separately published plugins should run their own CI (lint/build/test) and validate
openclaw.extensionspoints at the built entrypoint (dist/index.js).