Import map, registration API reference, and SDK architecture
You need to know which SDK subpath to import from
You want a reference for all registration methods on OpenClawPluginApi
You are looking up a specific SDK export
Plugin SDK Overview
The plugin SDK is the typed contract between plugins and core. This page is the
reference for what to import and what you can register.
**Looking for a how-to guide?**
- First plugin? Start with [Getting Started](/plugins/building-plugins)
- Channel plugin? See [Channel Plugins](/plugins/sdk-channel-plugins)
- Provider plugin? See [Provider Plugins](/plugins/sdk-provider-plugins)
Each subpath is a small, self-contained module. This keeps startup fast and
prevents circular dependency issues. For channel-specific entry/build helpers,
prefer openclaw/plugin-sdk/channel-core; keep openclaw/plugin-sdk/core for
the broader umbrella surface and shared helpers such as
buildChannelConfigSchema.
Do not add or depend on provider-named convenience seams such as
openclaw/plugin-sdk/slack, openclaw/plugin-sdk/discord,
openclaw/plugin-sdk/signal, or openclaw/plugin-sdk/whatsapp. Bundled plugins should compose generic SDK
subpaths inside their own api.ts or runtime-api.ts barrels, and core should
either use those plugin-local barrels or add a narrow generic SDK contract when
the need is truly cross-channel.
The generated export map still contains a small set of bundled-plugin helper
seams such as plugin-sdk/feishu, plugin-sdk/feishu-setup,
plugin-sdk/zalo, plugin-sdk/zalo-setup, and plugin-sdk/matrix*. Those
subpaths exist for bundled-plugin maintenance and compatibility only; they are
intentionally omitted from the common table below and are not the recommended
import path for new third-party plugins.
Subpath reference
The most commonly used subpaths, grouped by purpose. The generated full list of
200+ subpaths lives in scripts/lib/plugin-sdk-entrypoints.json.
Reserved bundled-plugin helper subpaths still appear in that generated list.
Treat those as implementation detail/compatibility surfaces unless a doc page
explicitly promotes one as public.
The register(api) callback receives an OpenClawPluginApi object with these
methods:
Capability registration
Method
What it registers
api.registerProvider(...)
Text inference (LLM)
api.registerCliBackend(...)
Local CLI inference backend
api.registerChannel(...)
Messaging channel
api.registerSpeechProvider(...)
Text-to-speech / STT synthesis
api.registerRealtimeTranscriptionProvider(...)
Streaming realtime transcription
api.registerRealtimeVoiceProvider(...)
Duplex realtime voice sessions
api.registerMediaUnderstandingProvider(...)
Image/audio/video analysis
api.registerImageGenerationProvider(...)
Image generation
api.registerVideoGenerationProvider(...)
Video generation
api.registerWebFetchProvider(...)
Web fetch / scrape provider
api.registerWebSearchProvider(...)
Web search
Tools and commands
Method
What it registers
api.registerTool(tool, opts?)
Agent tool (required or { optional: true })
api.registerCommand(def)
Custom command (bypasses the LLM)
Infrastructure
Method
What it registers
api.registerHook(events, handler, opts?)
Event hook
api.registerHttpRoute(params)
Gateway HTTP endpoint
api.registerGatewayMethod(name, handler)
Gateway RPC method
api.registerCli(registrar, opts?)
CLI subcommand
api.registerService(service)
Background service
api.registerInteractiveHandler(registration)
Interactive handler
Reserved core admin namespaces (config.*, exec.approvals.*, wizard.*,
update.*) always stay operator.admin, even if a plugin tries to assign a
narrower gateway method scope. Prefer plugin-specific prefixes for
plugin-owned methods.
CLI registration metadata
api.registerCli(registrar, opts?) accepts two kinds of top-level metadata:
commands: explicit command roots owned by the registrar
descriptors: parse-time command descriptors used for root CLI help,
routing, and lazy plugin CLI registration
If you want a plugin command to stay lazy-loaded in the normal root CLI path,
provide descriptors that cover every top-level command root exposed by that
registrar.
api.registerCli(async({program})=>{const{registerMatrixCli}=awaitimport("./src/cli.js");registerMatrixCli({program});},{descriptors:[{name:"matrix",description:"Manage Matrix accounts, verification, devices, and profile state",hasSubcommands: true,},],},);
Use commands by itself only when you do not need lazy root CLI registration.
That eager compatibility path remains supported, but it does not install
descriptor-backed placeholders for parse-time lazy loading.
CLI backend registration
api.registerCliBackend(...) lets a plugin own the default config for a local
AI CLI backend such as claude-cli or codex-cli.
The backend id becomes the provider prefix in model refs like claude-cli/opus.
The backend config uses the same shape as agents.defaults.cliBackends.<id>.
User config still wins. OpenClaw merges agents.defaults.cliBackends.<id> over the
plugin default before running the CLI.
Use normalizeConfig when a backend needs compatibility rewrites after merge
(for example normalizing old flag shapes).
Exclusive slots
Method
What it registers
api.registerContextEngine(id, factory)
Context engine (one active at a time)
api.registerMemoryPromptSection(builder)
Memory prompt section builder
api.registerMemoryFlushPlan(resolver)
Memory flush plan resolver
api.registerMemoryRuntime(runtime)
Memory runtime adapter
Memory embedding adapters
Method
What it registers
api.registerMemoryEmbeddingProvider(adapter)
Memory embedding adapter for the active plugin
registerMemoryPromptSection, registerMemoryFlushPlan, and
registerMemoryRuntime are exclusive to memory plugins.
registerMemoryEmbeddingProvider lets the active memory plugin register one
or more embedding adapter ids (for example openai, gemini, or a custom
plugin-defined id).
User config such as agents.defaults.memorySearch.provider and
agents.defaults.memorySearch.fallback resolves against those registered
adapter ids.
Events and lifecycle
Method
What it does
api.on(hookName, handler, opts?)
Typed lifecycle hook
api.onConversationBindingResolved(handler)
Conversation binding callback
Hook decision semantics
before_tool_call: returning { block: true } is terminal. Once any handler sets it, lower-priority handlers are skipped.
before_tool_call: returning { block: false } is treated as no decision (same as omitting block), not as an override.
before_install: returning { block: true } is terminal. Once any handler sets it, lower-priority handlers are skipped.
before_install: returning { block: false } is treated as no decision (same as omitting block), not as an override.
message_sending: returning { cancel: true } is terminal. Once any handler sets it, lower-priority handlers are skipped.
message_sending: returning { cancel: false } is treated as no decision (same as omitting cancel), not as an override.
API object fields
Field
Type
Description
api.id
string
Plugin id
api.name
string
Display name
api.version
string?
Plugin version (optional)
api.description
string?
Plugin description (optional)
api.source
string
Plugin source path
api.rootDir
string?
Plugin root directory (optional)
api.config
OpenClawConfig
Current config snapshot (active in-memory runtime snapshot when available)
api.pluginConfig
Record<string, unknown>
Plugin-specific config from plugins.entries.<id>.config
Current load mode; "setup-runtime" is the lightweight pre-full-entry startup/setup window
api.resolvePath(input)
(string) => string
Resolve path relative to plugin root
Internal module convention
Within your plugin, use local barrel files for internal imports:
my-plugin/
api.ts # Public exports for external consumers
runtime-api.ts # Internal-only runtime exports
index.ts # Plugin entry point
setup-entry.ts # Lightweight setup-only entry (optional)
Never import your own plugin through `openclaw/plugin-sdk/`
from production code. Route internal imports through `./api.ts` or
`./runtime-api.ts`. The SDK path is the external contract only.
Facade-loaded bundled plugin public surfaces (api.ts, runtime-api.ts,
index.ts, setup-entry.ts, and similar public entry files) now prefer the
active runtime config snapshot when OpenClaw is already running. If no runtime
snapshot exists yet, they fall back to the resolved config file on disk.
Provider plugins can also expose a narrow plugin-local contract barrel when a
helper is intentionally provider-specific and does not belong in a generic SDK
subpath yet. Current bundled example: the Anthropic provider keeps its Claude
stream helpers in its own public api.ts / contract-api.ts seam instead of
promoting Anthropic beta-header and service_tier logic into a generic
plugin-sdk/* contract.
@openclaw/openrouter-provider: api.ts exports the provider builder plus
onboarding/config helpers
Extension production code should also avoid `openclaw/plugin-sdk/`
imports. If a helper is truly shared, promote it to a neutral SDK subpath
such as `openclaw/plugin-sdk/speech`, `.../provider-model-shared`, or another
capability-oriented surface instead of coupling two plugins together.
Related
Entry Points — definePluginEntry and defineChannelPluginEntry options