Plugins โ Extensible Plugin Ecosystem
Manifest-driven plugin discovery, capability registration, and MCP tool bridging
๐ก What is this?
The plugins module is the extensibility backbone of OpenClaw. It manages how plugins are discovered, loaded, configured, and wired into the assistant lifecycle. Every channel adapter, LLM provider, memory engine, and tool extension is a plugin. The system uses a manifest-first approach: plugin metadata (openclaw.plugin.json) is parsed and validated before any plugin code runs, enabling fast config validation and auto-enable logic without importing heavy runtime dependencies.
๐ Analogy โ how does this work?
Architecture
The plugin system is organized into four major subsystems: Discovery scans plugin roots and parses manifests into a cache; Loading evaluates enable/disable config state and uses jiti to lazily import plugin code; Registration builds the plugin API surface and populates the central registry with hooks, tools, and channels; and the MCP Bridge extracts MCP server configuration from bundles for external tool serving.
flowchart TD
subgraph Discovery["Plugin Discovery"]
Roots["Plugin Roots"]
Scan["Directory Scanner"]
Manifest["Manifest Parser"]
Cache["Discovery Cache"]
end
subgraph Loading["Plugin Loading"]
ConfigState["Config State"]
Enable["Enable/Disable Logic"]
Jiti["Jiti Loader"]
Runtime["Plugin Runtime"]
end
subgraph Registration["Capability Registration"]
API["Plugin API Builder"]
Registry["Plugin Registry"]
Hooks["Hook Runner"]
Tools["Tool Factory"]
end
subgraph Bridge["MCP Bridge"]
BundleMCP["Bundle MCP Config"]
MCPServers["MCP Server Map"]
ToolServe["Tool Serving"]
end
Roots --> Scan
Scan --> Manifest
Manifest --> Cache
Cache --> ConfigState
ConfigState --> Enable
Enable --> Jiti
Jiti --> Runtime
Runtime --> API
API --> Registry
Registry --> Hooks
Registry --> Tools
Runtime --> BundleMCP
BundleMCP --> MCPServers
MCPServers --> ToolServe
Plugin Lifecycle
The lifecycle of a plugin spans from discovery through registration to runtime hook execution. The application kicks off discovery by scanning configured plugin roots. Each discovered manifest is loaded into a registry of static metadata. The runtime then resolves enable states, lazily imports plugin modules via jiti, and calls each plugin's register() function with a typed API surface. Hooks registered during this phase are later invoked by the hook runner with priority-based ordering.
sequenceDiagram
participant App as Application
participant Disc as Discovery
participant Man as Manifest Registry
participant Loader as Plugin Loader
participant Reg as Plugin Registry
participant Hook as Hook Runner
App->>Disc: discoverOpenClawPlugins(roots)
Disc->>Man: loadPluginManifestRegistry()
Man-->>Disc: manifest[] (static metadata)
Disc-->>App: PluginCandidate[]
App->>Loader: resolveRuntimePluginRegistry()
Loader->>Loader: resolveEffectiveEnableState()
Loader->>Loader: createJiti() + import plugin
Loader->>Reg: buildPluginApi() โ register()
Reg->>Reg: registerHook / registerTool / registerChannel
Reg-->>Loader: PluginRegistry
App->>Hook: runHook('before_agent_start', event)
Hook->>Hook: sort by priority
Hook->>Hook: execute handlers sequentially
Hook-->>App: accumulated result
Key Types
The plugin system defines a rich set of TypeScript types and interfaces that form the contract between the host application and plugin code.
| Name | File | Type | Description |
|---|---|---|---|
PluginManifest | manifest.ts | Interface | Static plugin metadata: id, schema, capabilities, providers, channels |
PluginRegistry | registry.ts | Class | Central registry holding all registered hooks, tools, channels, providers |
PluginRuntime | runtime/types.ts | Interface | Active plugin state during execution with config and logger |
OpenClawPluginApi | types.ts | Interface | The API surface given to plugins for registration (registerTool, registerHook, etc.) |
PluginHookRegistration | types.ts | Type | Hook handler with priority, pluginId, and execution context |
BundlePluginManifest | bundle-manifest.ts | Type | Bundle-format manifest for Codex/Claude/Cursor plugin formats |
PluginCandidate | discovery.ts | Type | Discovered plugin with source, format, package metadata |
BuildPluginApiParams | api-builder.ts | Type | Parameters for constructing the plugin API facade |
Hook Execution Pipeline
Hooks are the primary extension mechanism in the plugin system. When the application invokes runHook(name, event), the hook runner collects all registered handlers for that hook name, sorts them by priority (highest first, with pluginId as a tiebreaker for equal priorities), and executes them sequentially. Each handler receives the current accumulated result and can transform or extend it. Results are merged via mergeResults(). A handler can signal early exit through shouldStop(), which terminates the pipeline and logs the terminal decision. This allows plugins to override default behavior or short-circuit processing when needed.
flowchart LR
Sort["Sort by Priority"] --> Exec["Execute Handler"]
Exec --> Merge["mergeResults(acc, next)"]
Merge --> Stop{"shouldStop?"}
Stop -->|No| Exec
Stop -->|Yes| Return["Return Result"]
Merge -->|All done| Return
Sub-modules
The plugin system is composed of several focused sub-modules, each handling a distinct concern in the plugin lifecycle.
Directory Map
โโโ manifest.ts # Plugin manifest parsing (openclaw.plugin.json)
โโโ registry.ts # Central plugin registry
โโโ loader.ts # Plugin discovery & lazy loading
โโโ discovery.ts # Plugin candidate scanning
โโโ hooks.ts # Hook runner with priority ordering
โโโ api-builder.ts # Plugin API facade constructor
โโโ tools.ts # Plugin tool resolution
โโโ config-state.ts # Enable/disable state management
โโโ types.ts # Core plugin type definitions
โโโ bundle-manifest.ts # Bundle format manifest loading
โโโ bundle-commands.ts # Bundle command spec parsing
โโโ bundle-mcp.ts # MCP server config extraction
โโโ bundle-lsp.ts # LSP integration support
โโโ bundled-capability-runtime.ts # Bundled plugin runtime
โโโ provider-auth-*.ts # Provider authentication (13 files)
โโโ provider-*.ts # Provider management (20+ files)
โโโ memory-*.ts # Memory plugin support
โโโ runtime-*.ts # Channel runtime adapters
โโโ runtime/ # Runtime type definitions (9 files)
โโโ contracts/ # Contract test suites
โโโ *.test.ts # Unit tests (100+ files)
๐ src/plugin-sdk/ โ 337 files
โโโ index.ts # Main SDK entry (re-exports)
โโโ core.ts # Core plugin/channel/provider types
โโโ plugin-runtime.ts # Runtime command/hook helpers
โโโ plugin-entry.ts # Plugin entry point utilities
โโโ channel-*.ts # Channel abstractions (25+ files)
โโโ provider-*.ts # Provider abstractions (25+ files)
โโโ agent-runtime.ts # Agent runtime abstractions
โโโ memory-core*.ts # Memory engine SDK (20+ files)
โโโ discord*.ts, slack*.ts, ... # Channel implementations (80+ files)
โโโ openai.ts, anthropic*.ts, ... # LLM provider SDKs (60+ files)
โโโ testing.ts # Test utilities
Code Examples
Plugin Registration
A plugin exports a default object with a register() method. The API surface passed to register() provides typed methods for hooking into the lifecycle, registering tools, and declaring capabilities.
// Example: Registering a plugin with hooks and tools
import type { OpenClawPluginDefinition } from "./types.js";
export default {
register(api) {
// Register a lifecycle hook
api.registerHook("before_agent_start", {
priority: 100,
handler: async (event) => {
api.logger.info("Agent starting for", event.channelId);
return { extraSystemPrompt: "You are helpful." };
},
});
// Register a custom tool
api.registerTool({
name: "my-plugin-lookup",
description: "Looks up data from my service",
parameters: { query: { type: "string" } },
execute: async ({ query }) => {
return { result: await fetchFromService(query) };
},
});
},
} satisfies OpenClawPluginDefinition;
Plugin Manifest
Every plugin must include an openclaw.plugin.json manifest that declares its identity, config schema, and capabilities. The manifest is validated statically before any plugin code is imported.
// openclaw.plugin.json โ Plugin manifest
{
"id": "my-custom-plugin",
"name": "My Custom Plugin",
"version": "1.0.0",
"enabledByDefault": false,
"configSchema": {
"apiEndpoint": { "type": "string", "default": "https://api.example.com" }
},
"providers": ["my-provider"],
"providerAuthEnvVars": {
"my-provider": ["MY_PROVIDER_API_KEY"]
}
}