Caricamento in corso...
Caricamento in corso...
Last synced: Today, 22:00
Technical reference for the OpenClaw framework. Real-time synchronization with the official documentation engine.
Use this file to discover all available pages before exploring further.
This is the deep architecture reference for the OpenClaw plugin system. For practical guides, start with one of the focused pages below.
End-user guide for adding, enabling, and troubleshooting plugins.
First-plugin tutorial with the smallest working manifest.
Build a messaging channel plugin.
Build a model provider plugin.
Import map and registration API reference.
Capabilities are the public native plugin model inside OpenClaw. Every native OpenClaw plugin registers against one or more capability types:
| Capability | Registration method | Example plugins |
|---|---|---|
| Text inference | text api.registerProvider(...) | text openaitext anthropic |
| CLI inference backend | text api.registerCliBackend(...) | text openaitext anthropic |
| Speech | text api.registerSpeechProvider(...) | text elevenlabstext microsoft |
| Realtime transcription | text api.registerRealtimeTranscriptionProvider(...) | text openai |
| Realtime voice | text api.registerRealtimeVoiceProvider(...) | text openai |
| Media understanding | text api.registerMediaUnderstandingProvider(...) | text openaitext google |
| Image generation | text api.registerImageGenerationProvider(...) | text openaitext googletext faltext minimax |
| Music generation | text api.registerMusicGenerationProvider(...) | text googletext minimax |
| Video generation | text api.registerVideoGenerationProvider(...) | text qwen |
| Web fetch | text api.registerWebFetchProvider(...) | text firecrawl |
| Web search | text api.registerWebSearchProvider(...) | text google |
| Channel / messaging | text api.registerChannel(...) | text msteamstext matrix |
| Gateway discovery | text api.registerGatewayDiscoveryService(...) | text bonjour |
The capability model is landed in core and used by bundled/native plugins today, but external plugin compatibility still needs a tighter bar than "it is exported, therefore it is frozen."
| Plugin situation | Guidance |
|---|---|
| Existing external plugins | Keep hook-based integrations working; this is the compatibility baseline. |
| New bundled/native plugins | Prefer explicit capability registration over vendor-specific reach-ins or new hook-only designs. |
| External plugins adopting capability registration | Allowed, but treat capability-specific helper surfaces as evolving unless docs mark them stable. |
Capability registration is the intended direction. Legacy hooks remain the safest no-breakage path for external plugins during the transition. Exported helper subpaths are not all equal — prefer narrow documented contracts over incidental helper exports.
OpenClaw classifies every loaded plugin into a shape based on its actual registration behavior (not just static metadata):
Use
openclaw plugins inspect <id>The
before_agent_startDirection:
before_model_resolvebefore_prompt_buildWhen you run
openclaw doctoropenclaw plugins inspect <id>| Signal | Meaning |
|---|---|
| config valid | Config parses fine and plugins resolve |
| compatibility advisory | Plugin uses a supported-but-older pattern (e.g. text hook-only |
| legacy warning | Plugin uses text before_agent_start |
| hard error | Config is invalid or plugin failed to load |
Neither
hook-onlybefore_agent_starthook-onlybefore_agent_startopenclaw status --allopenclaw plugins doctorOpenClaw's plugin system has four layers:
For plugin CLI specifically, root command discovery is split in two phases:
registerCli(..., { descriptors: [...] })That keeps plugin-owned CLI code inside the plugin while still letting OpenClaw reserve root command names before parsing.
The important design boundary:
register(api)api.registrationMode === "full"That split lets OpenClaw validate config, explain missing/disabled plugins, and build UI/schema hints before the full runtime is active.
Gateway startup builds one
PluginMetadataSnapshotPlugin-aware config validation, startup auto-enable, and Gateway plugin bootstrap consume that snapshot instead of rebuilding manifest/index metadata independently.
PluginLookUpTableAfter startup, Gateway keeps the current metadata snapshot as a replaceable runtime product. Repeated runtime provider discovery can borrow that snapshot instead of reconstructing the installed index and manifest registry for each provider-catalog pass. The snapshot is cleared or replaced on Gateway shutdown, config/plugin inventory changes, and installed index writes; callers fall back to the cold manifest/index path when no compatible current snapshot exists. Compatibility checks must include plugin discovery roots such as
plugins.load.pathsThe snapshot and lookup table keep repeated startup decisions on the fast path:
The safety boundary is snapshot replacement, not mutation. Rebuild the snapshot when config, plugin inventory, install records, or persisted index policy changes. Do not treat it as a broad mutable global registry, and do not keep unbounded historical snapshots. Runtime plugin loading remains separate from metadata snapshots so stale runtime state cannot be hidden behind a metadata cache.
The cache rule is documented in Plugin architecture internals: manifest and discovery metadata are fresh unless a caller holds an explicit snapshot, lookup table, or manifest registry for the current flow. Hidden metadata caches and wall-clock TTLs are not part of plugin loading. Only runtime loader, module, and dependency-artifact caches may persist after code or installed artifacts are actually loaded.
Some cold-path callers still reconstruct manifest registries directly from the persisted installed plugin index instead of receiving a Gateway
PluginLookUpTableActivation planning is part of the control plane. Callers can ask which plugins are relevant to a concrete command, provider, channel, route, agent harness, or capability before loading broader runtime registries.
The planner keeps current manifest behavior compatible:
activation.*providerschannelscommandAliasessetup.providerscontracts.toolsChannel plugins do not need to register a separate send/edit/react tool for normal chat actions. OpenClaw keeps one shared
messageThe current boundary is:
messageFor channel plugins, the SDK surface is
ChannelMessageActionAdapter.describeMessageTool(...)When a channel-specific message-tool param carries a media source such as a local path or remote media URL, the plugin should also return
mediaSourceParamsdescribeMessageTool(...)sendCore passes runtime scope into that discovery step. Important fields include:
accountIdcurrentChannelIdcurrentThreadTscurrentMessageIdsessionKeysessionIdagentIdrequesterSenderIdThat matters for context-sensitive plugins. A channel can hide or expose message actions based on the active account, current room/thread/message, or trusted requester identity without hardcoding channel-specific branches in the core
messageThis is why embedded-runner routing changes are still plugin work: the runner is responsible for forwarding the current chat/session identity into the plugin discovery boundary so the shared
messageFor channel-owned execution helpers, bundled plugins should keep the execution runtime inside their own extension modules. Core no longer owns the Discord, Slack, Telegram, or WhatsApp message-action runtimes under
src/agents/toolsplugin-sdk/*-action-runtimeThe same boundary applies to provider-named SDK seams in general: core should not import channel-specific convenience barrels for Slack, Discord, Signal, WhatsApp, or similar extensions. If core needs a behavior, either consume the bundled plugin's own
api.tsruntime-api.tsBundled plugins follow the same rule. A bundled plugin's
runtime-api.tsopenclaw/plugin-sdk/<plugin-id>openclaw/plugin-sdk/channel-policyopenclaw/plugin-sdk/runtime-storeopenclaw/plugin-sdk/webhook-ingressFor polls specifically, there are two execution paths:
outbound.sendPollactions.handleAction("poll")Core now defers shared poll parsing until after plugin poll dispatch declines the action, so plugin-owned poll handlers can accept channel-specific poll fields without being blocked by the generic poll parser first.
See Plugin architecture internals for the full startup sequence.
OpenClaw treats a native plugin as the ownership boundary for a company or a feature, not as a grab bag of unrelated integrations.
That means:
The intended end state is:
This is the key distinction:
So if OpenClaw adds a new domain such as video, the first question is not "which provider should hardcode video handling?" The first question is "what is the core video capability contract?" Once that contract exists, vendor plugins can register against it and channel/feature plugins can consume it.
If the capability does not exist yet, the right move is usually:
This keeps ownership explicit while avoiding core behavior that depends on a single vendor or a one-off plugin-specific code path.
Use this mental model when deciding where code belongs:
For example, TTS follows this shape:
openaielevenlabsmicrosoftvoice-callThat same pattern should be preferred for future capabilities.
A company plugin should feel cohesive from the outside. If OpenClaw has shared contracts for models, speech, realtime transcription, realtime voice, media understanding, image generation, video generation, web fetch, and web search, a vendor can own all of its surfaces in one place:
tsimport type { OpenClawPluginDefinition } from "openclaw/plugin-sdk/plugin-entry"; import { describeImageWithModel, transcribeOpenAiCompatibleAudio, } from "openclaw/plugin-sdk/media-understanding"; const plugin: OpenClawPluginDefinition = { id: "exampleai", name: "ExampleAI", register(api) { api.registerProvider({ id: "exampleai", // auth/model catalog/runtime hooks }); api.registerSpeechProvider({ id: "exampleai", // vendor speech config — implement the SpeechProviderPlugin interface directly }); api.registerMediaUnderstandingProvider({ id: "exampleai", capabilities: ["image", "audio", "video"], async describeImage(req) { return describeImageWithModel({ provider: "exampleai", model: req.model, input: req.input, }); }, async transcribeAudio(req) { return transcribeOpenAiCompatibleAudio({ provider: "exampleai", model: req.model, input: req.input, }); }, }); api.registerWebSearchProvider( createPluginBackedWebSearchProvider({ id: "exampleai-search", // credential + fetch logic }), ); }, }; export default plugin;
What matters is not the exact helper names. The shape matters:
api.runtime.*OpenClaw already treats image/audio/video understanding as one shared capability. The same ownership model applies there:
That avoids baking one provider's video assumptions into core. The plugin owns the vendor surface; core owns the capability contract and fallback behavior.
Video generation already uses that same sequence: core owns the typed capability contract and runtime helper, and vendor plugins register
api.registerVideoGenerationProvider(...)Need a concrete rollout checklist? See Capability Cookbook.
The plugin API surface is intentionally typed and centralized in
OpenClawPluginApiWhy this matters:
There are two layers of enforcement:
The practical effect is that OpenClaw knows, up front, which plugin owns which surface. That lets core and channels compose seamlessly because ownership is declared, typed, and testable rather than implicit.
When in doubt, raise the abstraction level: define the capability first, then let plugins plug into it.
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.
Compatible bundles are safer by default because OpenClaw currently treats them as metadata/content packs. In current releases, that mostly means bundled skills.
Use allowlists and explicit install/load paths for non-bundled plugins. Treat workspace plugins as development-time code, not production defaults.
For bundled workspace package names, keep the plugin id anchored in the npm name:
@openclaw/<id>-provider-plugin-speech-sandbox-media-understandingOpenClaw exports capabilities, not implementation convenience.
Keep capability registration public. Trim non-contract helper exports:
Reserved bundled-plugin helper subpaths have been retired from the generated SDK export map. Keep owner-specific helpers inside the owning plugin package; promote only reusable host behavior to generic SDK contracts such as
plugin-sdk/gateway-runtimeplugin-sdk/security-runtimeplugin-sdk/plugin-config-runtimeFor the load pipeline, registry model, provider runtime hooks, Gateway HTTP routes, message tool schemas, channel target resolution, provider catalogs, context engine plugins, and the guide to adding a new capability, see Plugin architecture internals.
© 2024 TaskFlow Mirror
Powered by TaskFlow Sync Engine