Skip to content

Plugin Entry Points

Every plugin exports a default entry object. The SDK provides three helpers for creating them.

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({
/* ... */
});
},
});
FieldTypeRequiredDefault
idstringYes
namestringYes
descriptionstringYes
kindstringNo
configSchemaOpenClawPluginConfigSchema | () => OpenClawPluginConfigSchemaNoEmpty object schema
register(api: OpenClawPluginApi) => voidYes
  • id must match your openclaw.plugin.json manifest.
  • kind is for exclusive slots: "memory" or "context-engine".
  • configSchema can be a function for lazy evaluation.

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(/* ... */);
},
});
FieldTypeRequiredDefault
idstringYes
namestringYes
descriptionstringYes
pluginChannelPluginYes
configSchemaOpenClawPluginConfigSchema | () => OpenClawPluginConfigSchemaNoEmpty object schema
setRuntime(runtime: PluginRuntime) => voidNo
registerCliMetadata(api: OpenClawPluginApi) => voidNo
registerFull(api: OpenClawPluginApi) => voidNo
  • setRuntime is called during registration so you can store the runtime reference (typically via createPluginRuntimeStore). It is skipped during CLI metadata capture.
  • registerCliMetadata runs during both api.registrationMode === "cli-metadata" and api.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.
  • registerFull only runs when api.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 from registerCliMetadata(...) and keep registerFull(...) focused on runtime-only work.

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.

api.registrationMode tells your plugin how it was loaded:

ModeWhenWhat to register
"full"Normal gateway startupEverything
"setup-only"Disabled/unconfigured channelChannel registration only
"setup-runtime"Setup flow with runtime availableChannel + lightweight runtime
"cli-metadata"Root help / CLI metadata captureCLI 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 descriptors when 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 commands alone only for eager compatibility paths

OpenClaw classifies loaded plugins by their registration behavior:

ShapeDescription
plain-capabilityOne capability type (e.g. provider-only)
hybrid-capabilityMultiple capability types (e.g. provider + speech)
hook-onlyOnly hooks, no capabilities
non-capabilityTools/commands/services but no capabilities

Use openclaw plugins inspect <id> to see a plugin’s shape.