Plugins โ€” Extensible Plugin Ecosystem

Manifest-driven plugin discovery, capability registration, and MCP tool bridging

286Files
337SDK Files
25+Hook Types
TypeScriptLanguage
๐Ÿ”ต Manifest-Driven ๐ŸŸข Lazy Loading ๐ŸŸก Hook Pipeline ๐Ÿ”ด MCP Bridge

๐Ÿ’ก 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?
Think of the plugin system as a modular electrical panel in a building. Each plugin is a circuit breaker with a standardized interface. The manifest is the label on each breaker โ€” it tells the panel what the breaker controls without flipping it on. The registry is the panel itself, routing power (API calls) to the right circuits. The hook pipeline is the wiring bus โ€” events flow through it and each active breaker can intercept, transform, or block the current.

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.

NameFileTypeDescription
PluginManifestmanifest.tsInterfaceStatic plugin metadata: id, schema, capabilities, providers, channels
PluginRegistryregistry.tsClassCentral registry holding all registered hooks, tools, channels, providers
PluginRuntimeruntime/types.tsInterfaceActive plugin state during execution with config and logger
OpenClawPluginApitypes.tsInterfaceThe API surface given to plugins for registration (registerTool, registerHook, etc.)
PluginHookRegistrationtypes.tsTypeHook handler with priority, pluginId, and execution context
BundlePluginManifestbundle-manifest.tsTypeBundle-format manifest for Codex/Claude/Cursor plugin formats
PluginCandidatediscovery.tsTypeDiscovered plugin with source, format, package metadata
BuildPluginApiParamsapi-builder.tsTypeParameters 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

๐Ÿ“ src/plugins/ โ€” 286 files
โ”œโ”€โ”€ 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"]
  }
}