From ad4536fd7eb165b973f82c2031b2df2f7390e622 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 20 Mar 2026 10:44:11 -0700 Subject: [PATCH] docs: rename Extensions to Plugins, rewrite building guide as capability-agnostic, move voice-call to Channels --- docs/docs.json | 5 +- docs/plugins/building-extensions.md | 255 ++++++++++++++++++---------- docs/plugins/sdk-migration.md | 12 +- 3 files changed, 178 insertions(+), 94 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index 65e4ed25c1b..2b8d8d84a0c 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -948,6 +948,7 @@ "channels/telegram", "channels/tlon", "channels/twitch", + "plugins/voice-call", "channels/whatsapp", "channels/zalo", "channels/zalouser" @@ -1073,15 +1074,13 @@ ] }, { - "group": "Extensions", + "group": "Plugins", "pages": [ "plugins/building-extensions", "plugins/sdk-migration", "plugins/architecture", "plugins/community", "plugins/bundles", - "plugins/voice-call", - "plugins/zalouser", "plugins/manifest", "plugins/agent-tools", "tools/capability-cookbook" diff --git a/docs/plugins/building-extensions.md b/docs/plugins/building-extensions.md index 7b4548194cd..026ac4492de 100644 --- a/docs/plugins/building-extensions.md +++ b/docs/plugins/building-extensions.md @@ -1,79 +1,116 @@ --- -title: "Building Extensions" -summary: "Step-by-step guide for creating OpenClaw channel and provider extensions" +title: "Building Plugins" +sidebarTitle: "Building Plugins" +summary: "Step-by-step guide for creating OpenClaw plugins with any combination of capabilities" read_when: - - You want to create a new OpenClaw plugin or extension + - You want to create a new OpenClaw plugin - You need to understand the plugin SDK import patterns - - You are adding a new channel or provider to OpenClaw + - You are adding a new channel, provider, tool, or other capability to OpenClaw --- -# Building Extensions +# Building Plugins -Extensions add channels, model providers, tools, or other capabilities to OpenClaw. -This guide walks through creating one from scratch. +Plugins extend OpenClaw with new capabilities: channels, model providers, speech, +image generation, web search, agent tools, or any combination. A single plugin +can register multiple capabilities. + +OpenClaw encourages **external plugin development**. You do not need to add your +plugin to the OpenClaw repository. Publish your plugin on npm, and users install +it with `openclaw plugins install `. OpenClaw also maintains a set of +core plugins in-repo, but the plugin system is designed for independent ownership +and distribution. ## Prerequisites -- OpenClaw repository cloned and dependencies installed (`pnpm install`) +- Node >= 22 and a package manager (npm or pnpm) - Familiarity with TypeScript (ESM) +- For in-repo plugins: OpenClaw repository cloned and `pnpm install` done -## Extension structure +## Plugin capabilities -Every extension lives under `extensions//` and follows this layout: +A plugin can register one or more capabilities. The capability you register +determines what your plugin provides to OpenClaw: + +| Capability | Registration method | What it adds | +| ------------------- | --------------------------------------------- | ------------------------------ | +| Text inference | `api.registerProvider(...)` | Model provider (LLM) | +| Channel / messaging | `api.registerChannel(...)` | Chat channel (e.g. Slack, IRC) | +| Speech | `api.registerSpeechProvider(...)` | Text-to-speech / STT | +| Media understanding | `api.registerMediaUnderstandingProvider(...)` | Image/audio/video analysis | +| Image generation | `api.registerImageGenerationProvider(...)` | Image generation | +| Web search | `api.registerWebSearchProvider(...)` | Web search provider | +| Agent tools | `api.registerTool(...)` | Tools callable by the agent | + +A plugin that registers zero capabilities but provides hooks or services is a +**hook-only** plugin. That pattern is still supported. + +## Plugin structure + +Plugins follow this layout (whether in-repo or standalone): ``` -extensions/my-channel/ +my-plugin/ ├── package.json # npm metadata + openclaw config -├── index.ts # Entry point (defineChannelPluginEntry) +├── openclaw.plugin.json # Plugin manifest +├── index.ts # Entry point ├── setup-entry.ts # Setup wizard (optional) -├── api.ts # Public contract barrel (optional) -├── runtime-api.ts # Internal runtime barrel (optional) +├── api.ts # Public exports (optional) +├── runtime-api.ts # Internal exports (optional) └── src/ - ├── channel.ts # Channel adapter implementation + ├── provider.ts # Capability implementation ├── runtime.ts # Runtime wiring └── *.test.ts # Colocated tests ``` -## Create an extension +## Create a plugin - Create `extensions/my-channel/package.json`: + Create `package.json` with the `openclaw` metadata block. The structure + depends on what capabilities your plugin provides. + + **Channel plugin example:** ```json { - "name": "@openclaw/my-channel", - "version": "2026.1.1", - "description": "OpenClaw My Channel plugin", + "name": "@myorg/openclaw-my-channel", + "version": "1.0.0", "type": "module", - "dependencies": {}, "openclaw": { "extensions": ["./index.ts"], - "setupEntry": "./setup-entry.ts", "channel": { "id": "my-channel", "label": "My Channel", - "selectionLabel": "My Channel (plugin)", - "docsPath": "/channels/my-channel", - "docsLabel": "my-channel", - "blurb": "Short description of the channel.", - "order": 80 - }, - "install": { - "npmSpec": "@openclaw/my-channel", - "localPath": "extensions/my-channel" + "blurb": "Short description of the channel." } } } ``` - The `openclaw` field tells the plugin system what your extension provides. - For provider plugins, use `providers` instead of `channel`. + **Provider plugin example:** + + ```json + { + "name": "@myorg/openclaw-my-provider", + "version": "1.0.0", + "type": "module", + "openclaw": { + "extensions": ["./index.ts"], + "providers": ["my-provider"] + } + } + ``` + + The `openclaw` field tells the plugin system what your plugin provides. + A plugin can declare both `channel` and `providers` if it provides multiple + capabilities. - Create `extensions/my-channel/index.ts`: + The entry point registers your capabilities with the plugin API. + + **Channel plugin:** ```typescript import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core"; @@ -88,23 +125,51 @@ extensions/my-channel/ }); ``` - For provider plugins, use `definePluginEntry` instead. + **Provider plugin:** + + ```typescript + import { definePluginEntry } from "openclaw/plugin-sdk/core"; + + export default definePluginEntry({ + id: "my-provider", + name: "My Provider", + register(api) { + api.registerProvider({ + // Provider implementation + }); + }, + }); + ``` + + **Multi-capability plugin** (provider + tool): + + ```typescript + import { definePluginEntry } from "openclaw/plugin-sdk/core"; + + export default definePluginEntry({ + id: "my-plugin", + name: "My Plugin", + register(api) { + api.registerProvider({ /* ... */ }); + api.registerTool({ /* ... */ }); + api.registerImageGenerationProvider({ /* ... */ }); + }, + }); + ``` + + Use `defineChannelPluginEntry` for channel plugins and `definePluginEntry` + for everything else. A single plugin can register as many capabilities as needed. - - Always import from specific `openclaw/plugin-sdk/` paths rather than - the monolithic root. The old `openclaw/plugin-sdk/compat` barrel is deprecated - (see [SDK Migration](/plugins/sdk-migration)). + + Always import from specific `openclaw/plugin-sdk/\` paths. The old + monolithic import is deprecated (see [SDK Migration](/plugins/sdk-migration)). ```typescript // Correct: focused subpaths - import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core"; - import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline"; - import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing"; + import { definePluginEntry } from "openclaw/plugin-sdk/core"; import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store"; - import { createOptionalChannelSetupSurface } from "openclaw/plugin-sdk/channel-setup"; - import { resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy"; import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-oauth"; // Wrong: monolithic root (lint will reject this) @@ -114,10 +179,10 @@ extensions/my-channel/ | Subpath | Purpose | | --- | --- | - | `plugin-sdk/core` | Plugin entry definitions, base types | - | `plugin-sdk/channel-setup` | Optional setup adapters/wizards | + | `plugin-sdk/core` | Plugin entry definitions and base types | + | `plugin-sdk/channel-setup` | Setup wizard adapters | | `plugin-sdk/channel-pairing` | DM pairing primitives | - | `plugin-sdk/channel-reply-pipeline` | Prefix + typing reply wiring | + | `plugin-sdk/channel-reply-pipeline` | Reply prefix + typing wiring | | `plugin-sdk/channel-config-schema` | Config schema builders | | `plugin-sdk/channel-policy` | Group/DM policy helpers | | `plugin-sdk/secret-input` | Secret input parsing/helpers | @@ -130,95 +195,115 @@ extensions/my-channel/ | `plugin-sdk/testing` | Test utilities | - Use the narrowest primitive that matches the job. Reach for `channel-runtime` - or other larger helper barrels only when a dedicated subpath does not exist yet. + Use the narrowest subpath that matches the job. - - Within your extension, create barrel files for internal code sharing instead - of importing through the plugin SDK: + + Within your plugin, create local module files for internal code sharing + instead of re-importing through the plugin SDK: ```typescript - // api.ts — public contract for this extension - export { MyChannelConfig } from "./src/config.js"; - export { MyChannelRuntime } from "./src/runtime.js"; + // api.ts — public exports for this plugin + export { MyConfig } from "./src/config.js"; + export { MyRuntime } from "./src/runtime.js"; - // runtime-api.ts — internal-only exports (not for production consumers) + // runtime-api.ts — internal-only exports export { internalHelper } from "./src/helpers.js"; ``` - Never import your own extension back through its published SDK contract - path from production files. Route internal imports through `./api.ts` or - `./runtime-api.ts` instead. The SDK contract is for external consumers only. + Never import your own plugin back through its published SDK path from + production files. Route internal imports through local files like `./api.ts` + or `./runtime-api.ts`. The SDK path is for external consumers only. - Create `openclaw.plugin.json` in your extension root: + Create `openclaw.plugin.json` in your plugin root: ```json { - "id": "my-channel", - "kind": "channel", - "channels": ["my-channel"], - "name": "My Channel Plugin", - "description": "Connects OpenClaw to My Channel" + "id": "my-plugin", + "kind": "provider", + "name": "My Plugin", + "description": "Adds My Provider to OpenClaw" } ``` - See [Plugin manifest](/plugins/manifest) for the full schema. + For channel plugins, set `"kind": "channel"` and add `"channels": ["my-channel"]`. + + See [Plugin Manifest](/plugins/manifest) for the full schema. - - OpenClaw runs contract tests against all registered plugins. After adding your - extension, run: + + **External plugins:** run your own test suite against the plugin SDK contracts. + + **In-repo plugins:** OpenClaw runs contract tests against all registered plugins: ```bash pnpm test:contracts:channels # channel plugins pnpm test:contracts:plugins # provider plugins ``` - Contract tests verify your plugin conforms to the expected interface (setup - wizard, session binding, message handling, group policy, etc.). - - For unit tests, import test helpers from the public testing surface: + For unit tests, import test helpers from the testing surface: ```typescript import { createTestRuntime } from "openclaw/plugin-sdk/testing"; ``` + + + **External plugins:** publish to npm, then install: + + ```bash + npm publish + openclaw plugins install @myorg/openclaw-my-plugin + ``` + + **In-repo plugins:** place the plugin under `extensions/` and it is + automatically discovered during build. + + Users can browse and install community plugins with: + + ```bash + openclaw plugins search + openclaw plugins install + ``` + + -## Lint enforcement +## Lint enforcement (in-repo plugins) -Three scripts enforce SDK boundaries: +Three scripts enforce SDK boundaries for plugins in the OpenClaw repository: 1. **No monolithic root imports** — `openclaw/plugin-sdk` root is rejected -2. **No direct src/ imports** — extensions cannot import `../../src/` directly -3. **No self-imports** — extensions cannot import their own `plugin-sdk/` subpath +2. **No direct src/ imports** — plugins cannot import `../../src/` directly +3. **No self-imports** — plugins cannot import their own `plugin-sdk/\` subpath Run `pnpm check` to verify all boundaries before committing. +External plugins are not subject to these lint rules, but following the same +patterns is strongly recommended. + ## Pre-submission checklist **package.json** has correct `openclaw` metadata Entry point uses `defineChannelPluginEntry` or `definePluginEntry` -All imports use focused `plugin-sdk/` paths -Internal imports use local barrels, not SDK self-imports +All imports use focused `plugin-sdk/\` paths +Internal imports use local modules, not SDK self-imports `openclaw.plugin.json` manifest is present and valid -Contract tests pass (`pnpm test:contracts`) -Unit tests colocated as `*.test.ts` -`pnpm check` passes (lint + format) -Doc page created under `docs/channels/` or `docs/plugins/` +Tests pass +`pnpm check` passes (in-repo plugins) ## Related -- [Plugin SDK Migration](/plugins/sdk-migration) — migrating from compat to focused subpaths +- [Plugin SDK Migration](/plugins/sdk-migration) — migrating from the deprecated compat import - [Plugin Architecture](/plugins/architecture) — internals and capability model - [Plugin Manifest](/plugins/manifest) — full manifest schema -- [Community Plugins](/plugins/community) — existing community extensions +- [Plugin Agent Tools](/plugins/agent-tools) — adding agent tools in a plugin +- [Community Plugins](/plugins/community) — listing and quality bar diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index 45c163cd0ed..fb745e46e91 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -16,7 +16,7 @@ what changed, why, and how to migrate. ## Why this change -The monolithic compat barrel re-exported everything from a single entry point. +The monolithic compat entry re-exported everything from a single entry point. This caused: - **Slow startup**: importing one helper pulled in dozens of unrelated modules. @@ -28,14 +28,14 @@ with a clear purpose. ## What triggers the warning -If your plugin imports from the compat barrel, you will see: +If your plugin imports from the compat entry, you will see: ``` [OPENCLAW_PLUGIN_SDK_COMPAT_DEPRECATED] Warning: openclaw/plugin-sdk/compat is deprecated for new plugins. Migrate to focused openclaw/plugin-sdk/\ imports. ``` -The compat barrel still works at runtime. This is a deprecation warning, not an +The compat entry still works at runtime. This is a deprecation warning, not an error. But new plugins **must not** use it, and existing plugins should migrate before compat is removed. @@ -54,7 +54,7 @@ grep -r "plugin-sdk/compat" extensions/my-plugin/ Each export from compat maps to a specific subpath. Replace the import source: ```typescript -// Before (compat barrel) +// Before (compat entry) import { createChannelReplyPipeline, createPluginRuntimeStore, @@ -106,8 +106,8 @@ check the source at `src/plugin-sdk/` or ask in Discord. ## Compat barrel removal timeline -- **Now**: compat barrel emits a deprecation warning at runtime. -- **Next major release**: compat barrel will be removed. Plugins still using it will +- **Now**: compat entry emits a deprecation warning at runtime. +- **Next major release**: compat entry will be removed. Plugins still using it will fail to import. Bundled plugins (under `extensions/`) have already been migrated. External plugins