Plugin Entry Points
Plugin Entry Points
Section titled “Plugin Entry Points”Every plugin exports a default entry object. The SDK provides three helpers for creating them.
definePluginEntry
Section titled “definePluginEntry”Import: openclaw/plugin-sdk/plugin-entry
For provider plugins, tool plugins, hook plugins, and anything that is not a messaging channel.
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
export default definePluginEntry({ id: "my-plugin", name: "My Plugin", description: "Short summary", register(api) { api.registerProvider({ /* ... */ }); api.registerTool({ /* ... */ }); },});| Field | Type | Required | Default |
|---|---|---|---|
id | string | Yes | — |
name | string | Yes | — |
description | string | Yes | — |
kind | string | No | — |
configSchema | OpenClawPluginConfigSchema | () => OpenClawPluginConfigSchema | No | Empty object schema |
register | (api: OpenClawPluginApi) => void | Yes | — |
idmust match youropenclaw.plugin.jsonmanifest.kindis for exclusive slots:"memory"or"context-engine".configSchemacan be a function for lazy evaluation.
defineChannelPluginEntry
Section titled “defineChannelPluginEntry”Import: openclaw/plugin-sdk/core
Wraps definePluginEntry with channel-specific wiring. Automatically calls
api.registerChannel({ plugin }), exposes an optional root-help CLI metadata
seam, and gates registerFull on registration mode.
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
export default defineChannelPluginEntry({ id: "my-channel", name: "My Channel", description: "Short summary", plugin: myChannelPlugin, setRuntime: setMyRuntime, registerCliMetadata(api) { api.registerCli(/* ... */); }, registerFull(api) { api.registerGatewayMethod(/* ... */); },});| Field | Type | Required | Default |
|---|---|---|---|
id | string | Yes | — |
name | string | Yes | — |
description | string | Yes | — |
plugin | ChannelPlugin | Yes | — |
configSchema | OpenClawPluginConfigSchema | () => OpenClawPluginConfigSchema | No | Empty object schema |
setRuntime | (runtime: PluginRuntime) => void | No | — |
registerCliMetadata | (api: OpenClawPluginApi) => void | No | — |
registerFull | (api: OpenClawPluginApi) => void | No | — |
setRuntimeis called during registration so you can store the runtime reference (typically viacreatePluginRuntimeStore). It is skipped during CLI metadata capture.registerCliMetadataruns during bothapi.registrationMode === "cli-metadata"andapi.registrationMode === "full". Use it as the canonical place for channel-owned CLI descriptors so root help stays non-activating while normal CLI command registration remains compatible with full plugin loads.registerFullonly runs whenapi.registrationMode === "full". It is skipped during setup-only loading.- For plugin-owned root CLI commands, prefer
api.registerCli(..., { descriptors: [...] })when you want the command to stay lazy-loaded without disappearing from the root CLI parse tree. For channel plugins, prefer registering those descriptors fromregisterCliMetadata(...)and keepregisterFull(...)focused on runtime-only work.
defineSetupPluginEntry
Section titled “defineSetupPluginEntry”Import: openclaw/plugin-sdk/core
For the lightweight setup-entry.ts file. Returns just { plugin } with no
runtime or CLI wiring.
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
export default defineSetupPluginEntry(myChannelPlugin);OpenClaw loads this instead of the full entry when a channel is disabled, unconfigured, or when deferred loading is enabled. See Setup and Config for when this matters.
Registration mode
Section titled “Registration mode”api.registrationMode tells your plugin how it was loaded:
| Mode | When | What to register |
|---|---|---|
"full" | Normal gateway startup | Everything |
"setup-only" | Disabled/unconfigured channel | Channel registration only |
"setup-runtime" | Setup flow with runtime available | Channel + lightweight runtime |
"cli-metadata" | Root help / CLI metadata capture | CLI descriptors only |
defineChannelPluginEntry handles this split automatically. If you use
definePluginEntry directly for a channel, check mode yourself:
register(api) { if (api.registrationMode === "cli-metadata" || api.registrationMode === "full") { api.registerCli(/* ... */); if (api.registrationMode === "cli-metadata") return; }
api.registerChannel({ plugin: myPlugin }); if (api.registrationMode !== "full") return;
// Heavy runtime-only registrations api.registerService(/* ... */);}For CLI registrars specifically:
- use
descriptorswhen the registrar owns one or more root commands and you want OpenClaw to lazy-load the real CLI module on first invocation - make sure those descriptors cover every top-level command root exposed by the registrar
- use
commandsalone only for eager compatibility paths
Plugin shapes
Section titled “Plugin shapes”OpenClaw classifies loaded plugins by their registration behavior:
| Shape | Description |
|---|---|
| plain-capability | One capability type (e.g. provider-only) |
| hybrid-capability | Multiple capability types (e.g. provider + speech) |
| hook-only | Only hooks, no capabilities |
| non-capability | Tools/commands/services but no capabilities |
Use openclaw plugins inspect <id> to see a plugin’s shape.
Related
Section titled “Related”- SDK Overview — registration API and subpath reference
- Runtime Helpers —
api.runtimeandcreatePluginRuntimeStore - Setup and Config — manifest, setup entry, deferred loading
- Channel Plugins — building the
ChannelPluginobject - Provider Plugins — provider registration and hooks