Implementation:Openclaw Openclaw LoadOpenClawPlugins
loadOpenClawPlugins and discoverOpenClawPlugins
loadOpenClawPlugins is the main entry point for the plugin verification pipeline. It discovers installed plugins, loads their manifests, validates configuration, imports modules, executes registration callbacks, and produces a PluginRegistry. discoverOpenClawPlugins handles the discovery phase: scanning multiple directories for plugin candidates.
Principle:Openclaw_Openclaw_Extension_Verification
Source Location
| Function | File | Lines |
|---|---|---|
loadOpenClawPlugins |
src/plugins/loader.ts |
169-453 |
discoverOpenClawPlugins |
src/plugins/discovery.ts |
301-364 |
Repository: github.com/openclaw/openclaw
loadOpenClawPlugins
Signature
export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegistry
Options Type
export type PluginLoadOptions = {
config?: OpenClawConfig;
workspaceDir?: string;
logger?: PluginLogger;
coreGatewayHandlers?: Record<string, GatewayRequestHandler>;
cache?: boolean;
mode?: "full" | "validate";
};
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
config |
OpenClawConfig? |
{} |
The full OpenClaw configuration. |
workspaceDir |
string? |
undefined |
Workspace root for workspace-local plugins. |
logger |
PluginLogger? |
subsystem logger | Logger for plugin load messages. |
coreGatewayHandlers |
Record<string, GatewayRequestHandler>? |
undefined |
Core gateway handlers (plugins cannot override these). |
cache |
boolean? |
true |
Whether to cache and reuse the registry. |
mode |
"validate" | "full" |
In "validate" mode, plugins are loaded but their register() callbacks are not called.
|
Return Value
Returns PluginRegistry:
export type PluginRegistry = {
plugins: PluginRecord[];
tools: PluginToolRegistration[];
hooks: PluginHookRegistration[];
typedHooks: TypedPluginHookRegistration[];
channels: PluginChannelRegistration[];
providers: PluginProviderRegistration[];
gatewayHandlers: GatewayRequestHandlers;
httpHandlers: PluginHttpRegistration[];
httpRoutes: PluginHttpRouteRegistration[];
cliRegistrars: PluginCliRegistration[];
services: PluginServiceRegistration[];
commands: PluginCommandRegistration[];
diagnostics: PluginDiagnostic[];
};
Behavior
The function proceeds through these stages:
1. Cache check. Computes a cache key from the workspace dir and normalized plugins config. Returns the cached registry if available.
2. Setup. Clears previously registered plugin commands. Creates a fresh PluginRuntime and PluginRegistry.
3. Discovery. Calls discoverOpenClawPlugins() to find all plugin candidates.
4. Manifest loading. Calls loadPluginManifestRegistry() to read and parse package manifests.
5. SDK alias resolution. Resolves the openclaw/plugin-sdk path for the jiti TypeScript loader.
6. Per-candidate processing loop. For each candidate:
| Step | Check | On Failure |
|---|---|---|
| Manifest lookup | Candidate must have a manifest record | Skip candidate |
| Deduplication | Plugin ID must not already be seen | Record as disabled ("overridden by ...") |
| Enable state | Check allow/deny lists and per-entry config | Record as disabled with reason |
| Config schema | Manifest must declare a config schema | Record as error ("missing config schema") |
| Module import | jiti(candidate.source) must succeed |
Record as error with import error |
| ID mismatch | Module export ID vs manifest ID | Diagnostic warning (non-fatal) |
| Memory slot | Memory-kind plugins check slot assignment | Disable if not the selected memory plugin |
| Config validation | Per-plugin config validated against JSON schema | Record as error ("invalid config") |
| Validate-only mode | Skip registration in validate mode | Record as loaded (no registration) |
| Registration | Call register(api) |
Record as error if registration throws |
7. Memory slot check. After the loop, warns if the configured memory slot plugin was not found.
8. Finalization. Caches the registry, sets it as the active registry, and initializes the global hook runner.
Plugin Record Status Values
| Status | Meaning |
|---|---|
"loaded" |
Plugin loaded and registered successfully. |
"disabled" |
Plugin is disabled by config, allowlist, denylist, deduplication, or memory slot logic. |
"error" |
Plugin encountered an error during loading, validation, or registration. |
discoverOpenClawPlugins
Signature
export function discoverOpenClawPlugins(params: {
workspaceDir?: string;
extraPaths?: string[];
}): PluginDiscoveryResult
Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceDir |
string? |
Workspace root directory for workspace-local plugin discovery. |
extraPaths |
string[]? |
Additional filesystem paths to scan (from plugins.load.paths).
|
Return Value
export type PluginDiscoveryResult = {
candidates: PluginCandidate[];
diagnostics: PluginDiagnostic[];
};
Where each candidate contains:
export type PluginCandidate = {
idHint: string;
source: string;
rootDir: string;
origin: PluginOrigin;
workspaceDir?: string;
packageName?: string;
packageVersion?: string;
packageDescription?: string;
packageDir?: string;
packageManifest?: OpenClawPackageManifest;
};
Discovery Order
The function scans in this specific order:
- Extra paths (origin:
"config") -- each path is analyzed: if it is a file, it becomes a single candidate; if it is a directory withpackage.json, its extensions are resolved; otherwise, it is scanned recursively. - Workspace extensions (origin:
"workspace") -- scans<workspaceDir>/.openclaw/extensions/. - Global extensions (origin:
"global") -- scans~/.openclaw/extensions/. - Bundled extensions (origin:
"bundled") -- scans the built-in plugins directory.
A seen set prevents the same resolved file from appearing as multiple candidates.
Directory Scanning Logic
For each directory, the scanner (discoverInDirectory) checks:
- Top-level files with extension
.ts,.js,.mts,.cts,.mjs,.cjs(excluding.d.ts). - Subdirectories with
package.jsondeclaringopenclaw.extensions. - Subdirectories with an
index.tsorindex.jsentry point.
ID Hint Derivation
The idHint is derived from the package name (unscoped) or the filename:
- Scoped packages:
@openclaw/voice-callbecomesvoice-call. - Multi-extension packages:
voice-call/main(package name + base filename). - Bare files: the filename without extension.
Integration Flow
loadOpenClawPlugins() | +--> discoverOpenClawPlugins() --> PluginCandidate[] | +--> loadPluginManifestRegistry() --> manifest records | +--> for each candidate: | resolveEnableState() | jiti(source) | validatePluginConfig() | register(api) | +--> cache + setActivePluginRegistry() | +--> initializeGlobalHookRunner() | v PluginRegistry
Caching
The registry is cached by a composite key of the workspace directory and the serialized normalized plugins config. The cache can be bypassed by setting cache: false. When the cache is used, no discovery or loading occurs; the cached registry is returned directly.