Files
openclaw/docs/plugins/sdk-setup.md
2026-04-04 12:08:41 +01:00

11 KiB

title, sidebarTitle, summary, read_when
title sidebarTitle summary read_when
Plugin Setup and Config Setup and Config Setup wizards, setup-entry.ts, config schemas, and package.json metadata
You are adding a setup wizard to a plugin
You need to understand setup-entry.ts vs index.ts
You are defining plugin config schemas or package.json openclaw metadata

Plugin Setup and Config

Reference for plugin packaging (package.json metadata), manifests (openclaw.plugin.json), setup entries, and config schemas.

**Looking for a walkthrough?** The how-to guides cover packaging in context: [Channel Plugins](/plugins/sdk-channel-plugins#step-1-package-and-manifest) and [Provider Plugins](/plugins/sdk-provider-plugins#step-1-package-and-manifest).

Package metadata

Your package.json needs an openclaw field that tells the plugin system what your plugin provides:

Channel plugin:

{
  "name": "@myorg/openclaw-my-channel",
  "version": "1.0.0",
  "type": "module",
  "openclaw": {
    "extensions": ["./index.ts"],
    "setupEntry": "./setup-entry.ts",
    "channel": {
      "id": "my-channel",
      "label": "My Channel",
      "blurb": "Short description of the channel."
    }
  }
}

Provider plugin / ClawHub publish baseline:

{
  "name": "@myorg/openclaw-my-plugin",
  "version": "1.0.0",
  "type": "module",
  "openclaw": {
    "extensions": ["./index.ts"],
    "compat": {
      "pluginApi": ">=2026.3.24-beta.2",
      "minGatewayVersion": "2026.3.24-beta.2"
    },
    "build": {
      "openclawVersion": "2026.3.24-beta.2",
      "pluginSdkVersion": "2026.3.24-beta.2"
    }
  }
}

If you publish the plugin externally on ClawHub, those compat and build fields are required. The canonical publish snippets live in docs/snippets/plugin-publish/.

openclaw fields

Field Type Description
extensions string[] Entry point files (relative to package root)
setupEntry string Lightweight setup-only entry (optional)
channel object Channel metadata: id, label, blurb, selectionLabel, docsPath, order, aliases
providers string[] Provider ids registered by this plugin
install object Install hints: npmSpec, localPath, defaultChoice
startup object Startup behavior flags

Deferred full load

Channel plugins can opt into deferred loading with:

{
  "openclaw": {
    "extensions": ["./index.ts"],
    "setupEntry": "./setup-entry.ts",
    "startup": {
      "deferConfiguredChannelFullLoadUntilAfterListen": true
    }
  }
}

When enabled, OpenClaw loads only setupEntry during the pre-listen startup phase, even for already-configured channels. The full entry loads after the gateway starts listening.

Only enable deferred loading when your `setupEntry` registers everything the gateway needs before it starts listening (channel registration, HTTP routes, gateway methods). If the full entry owns required startup capabilities, keep the default behavior.

If your setup/full entry registers gateway RPC methods, keep them on a plugin-specific prefix. Reserved core admin namespaces (config.*, exec.approvals.*, wizard.*, update.*) stay core-owned and always resolve to operator.admin.

Plugin manifest

Every native plugin must ship an openclaw.plugin.json in the package root. OpenClaw uses this to validate config without executing plugin code.

{
  "id": "my-plugin",
  "name": "My Plugin",
  "description": "Adds My Plugin capabilities to OpenClaw",
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "webhookSecret": {
        "type": "string",
        "description": "Webhook verification secret"
      }
    }
  }
}

For channel plugins, add kind and channels:

{
  "id": "my-channel",
  "kind": "channel",
  "channels": ["my-channel"],
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {}
  }
}

Even plugins with no config must ship a schema. An empty schema is valid:

{
  "id": "my-plugin",
  "configSchema": {
    "type": "object",
    "additionalProperties": false
  }
}

See Plugin Manifest for the full schema reference.

ClawHub publishing

For plugin packages, use the package-specific ClawHub command:

clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin

The legacy skill-only publish alias is for skills. Plugin packages should always use clawhub package publish.

Setup entry

The setup-entry.ts file is a lightweight alternative to index.ts that OpenClaw loads when it only needs setup surfaces (onboarding, config repair, disabled channel inspection).

// setup-entry.ts
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
import { myChannelPlugin } from "./src/channel.js";

export default defineSetupPluginEntry(myChannelPlugin);

This avoids loading heavy runtime code (crypto libraries, CLI registrations, background services) during setup flows.

When OpenClaw uses setupEntry instead of the full entry:

  • The channel is disabled but needs setup/onboarding surfaces
  • The channel is enabled but unconfigured
  • Deferred loading is enabled (deferConfiguredChannelFullLoadUntilAfterListen)

What setupEntry must register:

  • The channel plugin object (via defineSetupPluginEntry)
  • Any HTTP routes required before gateway listen
  • Any gateway methods needed during startup

Those startup gateway methods should still avoid reserved core admin namespaces such as config.* or update.*.

What setupEntry should NOT include:

  • CLI registrations
  • Background services
  • Heavy runtime imports (crypto, SDKs)
  • Gateway methods only needed after startup

Config schema

Plugin config is validated against the JSON Schema in your manifest. Users configure plugins via:

{
  plugins: {
    entries: {
      "my-plugin": {
        config: {
          webhookSecret: "abc123",
        },
      },
    },
  },
}

Your plugin receives this config as api.pluginConfig during registration.

For channel-specific config, use the channel config section instead:

{
  channels: {
    "my-channel": {
      token: "bot-token",
      allowFrom: ["user1", "user2"],
    },
  },
}

Building channel config schemas

Use buildChannelConfigSchema from openclaw/plugin-sdk/core to convert a Zod schema into the ChannelConfigSchema wrapper that OpenClaw validates:

import { z } from "zod";
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/core";

const accountSchema = z.object({
  token: z.string().optional(),
  allowFrom: z.array(z.string()).optional(),
  accounts: z.object({}).catchall(z.any()).optional(),
  defaultAccount: z.string().optional(),
});

const configSchema = buildChannelConfigSchema(accountSchema);

Setup wizards

Channel plugins can provide interactive setup wizards for openclaw onboard. The wizard is a ChannelSetupWizard object on the ChannelPlugin:

import type { ChannelSetupWizard } from "openclaw/plugin-sdk/channel-setup";

const setupWizard: ChannelSetupWizard = {
  channel: "my-channel",
  status: {
    configuredLabel: "Connected",
    unconfiguredLabel: "Not configured",
    resolveConfigured: ({ cfg }) => Boolean((cfg.channels as any)?.["my-channel"]?.token),
  },
  credentials: [
    {
      inputKey: "token",
      providerHint: "my-channel",
      credentialLabel: "Bot token",
      preferredEnvVar: "MY_CHANNEL_BOT_TOKEN",
      envPrompt: "Use MY_CHANNEL_BOT_TOKEN from environment?",
      keepPrompt: "Keep current token?",
      inputPrompt: "Enter your bot token:",
      inspect: ({ cfg, accountId }) => {
        const token = (cfg.channels as any)?.["my-channel"]?.token;
        return {
          accountConfigured: Boolean(token),
          hasConfiguredValue: Boolean(token),
        };
      },
    },
  ],
};

The ChannelSetupWizard type supports credentials, textInputs, dmPolicy, allowFrom, groupAccess, prepare, finalize, and more. See bundled plugin packages (for example the Discord plugin src/channel.setup.ts) for full examples.

For DM allowlist prompts that only need the standard note -> prompt -> parse -> merge -> patch flow, prefer the shared setup helpers from openclaw/plugin-sdk/setup: createPromptParsedAllowFromForAccount(...), createTopLevelChannelParsedAllowFromPrompt(...), and createNestedChannelParsedAllowFromPrompt(...).

For channel setup status blocks that only vary by labels, scores, and optional extra lines, prefer createStandardChannelSetupStatus(...) from openclaw/plugin-sdk/setup instead of hand-rolling the same status object in each plugin.

For optional setup surfaces that should only appear in certain contexts, use createOptionalChannelSetupSurface from openclaw/plugin-sdk/channel-setup:

import { createOptionalChannelSetupSurface } from "openclaw/plugin-sdk/channel-setup";

const setupSurface = createOptionalChannelSetupSurface({
  channel: "my-channel",
  label: "My Channel",
  npmSpec: "@myorg/openclaw-my-channel",
  docsPath: "/channels/my-channel",
});
// Returns { setupAdapter, setupWizard }

Publishing and installing

External plugins: publish to ClawHub or npm, then install:

openclaw plugins install @myorg/openclaw-my-plugin

OpenClaw tries ClawHub first and falls back to npm automatically. You can also force a specific source:

openclaw plugins install clawhub:@myorg/openclaw-my-plugin   # ClawHub only
openclaw plugins install npm:@myorg/openclaw-my-plugin       # npm only

In-repo plugins: place under the bundled plugin workspace tree and they are automatically discovered during build.

Users can browse and install:

openclaw plugins search <query>
openclaw plugins install <package-name>
For npm-sourced installs, `openclaw plugins install` runs `npm install --ignore-scripts` (no lifecycle scripts). Keep plugin dependency trees pure JS/TS and avoid packages that require `postinstall` builds.