Skip to main content

Plugin Entry Points

Every plugin exports a default entry object. The SDK provides three helpers for creating them.
Looking for a walkthrough? See Channel Plugins or Provider Plugins for step-by-step guides.

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({
      /* ... */
    });
  },
});
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.
  • OpenClaw resolves and memoizes that schema on first access, so expensive schema builders only run once.

defineChannelPluginEntry

Import: openclaw/plugin-sdk/channel-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/channel-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.
  • Like definePluginEntry, configSchema can be a lazy factory and OpenClaw memoizes the resolved schema on first access.
  • 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.
  • If registerFull(...) also registers gateway RPC methods, keep them on a plugin-specific prefix. Reserved core admin namespaces (config.*, exec.approvals.*, wizard.*, update.*) are always coerced to operator.admin.

defineSetupPluginEntry

Import: openclaw/plugin-sdk/channel-core For the lightweight setup-entry.ts file. Returns just { plugin } with no runtime or CLI wiring.
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-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. In practice, pair defineSetupPluginEntry(...) with the narrow setup helper families:
  • openclaw/plugin-sdk/setup-runtime for runtime-safe setup helpers such as import-safe setup patch adapters, lookup-note output, promptResolvedAllowFrom, splitSetupEntries, and delegated setup proxies
  • openclaw/plugin-sdk/channel-setup for optional-install setup surfaces
  • openclaw/plugin-sdk/setup-tools for setup/install CLI/archive/docs helpers
Keep heavy SDKs, CLI registration, and long-lived runtime services in the full entry.

Registration mode

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 registration plus only the lightweight runtime needed before the full entry loads
"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(/* ... */);
}
Treat "setup-runtime" as the window where setup-only startup surfaces must exist without re-entering the full bundled channel runtime. Good fits are channel registration, setup-safe HTTP routes, setup-safe gateway methods, and delegated setup helpers. Heavy background services, CLI registrars, and provider/client SDK bootstraps still belong in "full". 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

Plugin shapes

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.