Skip to main content

Plugin Entry Points

OpenClaw has two main entry helpers:
  • definePluginEntry(...) for general plugins
  • defineChannelPluginEntry(...) for native messaging channels
There is also defineSetupPluginEntry(...) for a separate setup-only module.

definePluginEntry(...)

Use this for providers, tools, commands, services, memory plugins, and context engines.
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";

export default definePluginEntry({
  id: "example-tools",
  name: "Example Tools",
  description: "Adds a command and a tool",
  register(api: OpenClawPluginApi) {
    api.registerCommand({
      name: "example",
      description: "Show plugin status",
      handler: async () => ({ text: "example ok" }),
    });

    api.registerTool({
      name: "example_lookup",
      description: "Look up Example data",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string" },
        },
        required: ["query"],
      },
      async execute(_callId, params) {
        return {
          content: [{ type: "text", text: `lookup: ${String(params.query)}` }],
        };
      },
    });
  },
});

defineChannelPluginEntry(...)

Use this for a plugin that registers a ChannelPlugin.
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { channelPlugin } from "./src/channel.js";
import { setRuntime } from "./src/runtime.js";

export default defineChannelPluginEntry({
  id: "example-channel",
  name: "Example Channel",
  description: "Example messaging plugin",
  plugin: channelPlugin,
  setRuntime,
  registerFull(api) {
    api.registerTool({
      name: "example_channel_status",
      description: "Inspect Example Channel state",
      parameters: { type: "object", properties: {} },
      async execute() {
        return { content: [{ type: "text", text: "ok" }] };
      },
    });
  },
});

Why registerFull(...) exists

OpenClaw can load plugins in setup-focused registration modes. registerFull lets a channel plugin skip extra runtime-only registrations such as tools while still registering the channel capability itself. Use it for:
  • agent tools
  • gateway-only routes
  • runtime-only commands
Do not use it for the actual ChannelPlugin; that belongs in plugin: ....

defineSetupPluginEntry(...)

Use this when a channel ships a second module for setup flows.
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
import { exampleSetupPlugin } from "./src/channel.setup.js";

export default defineSetupPluginEntry(exampleSetupPlugin);
This keeps the setup entry shape explicit and matches the bundled channel pattern used in OpenClaw.

One plugin, many capabilities

A single entry file can register multiple capabilities:
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";

export default definePluginEntry({
  id: "example-hybrid",
  name: "Example Hybrid",
  description: "Provider plus tools",
  register(api: OpenClawPluginApi) {
    api.registerProvider({
      id: "example",
      label: "Example",
      auth: [],
    });

    api.registerTool({
      name: "example_ping",
      description: "Simple health check",
      parameters: { type: "object", properties: {} },
      async execute() {
        return { content: [{ type: "text", text: "pong" }] };
      },
    });
  },
});

Entry-file checklist

  • Give the plugin a stable id.
  • Keep name and description human-readable.
  • Put schema at the entry level when the plugin has config.
  • Register only public capabilities inside register(api).
  • Keep channel plugins on plugin-sdk/core.
  • Keep non-channel plugins on plugin-sdk/plugin-entry.