docs: rename Extensions to Plugins, rewrite building guide as capability-agnostic, move voice-call to Channels
This commit is contained in:
parent
1cabb053ad
commit
ad4536fd7e
@ -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"
|
||||
|
||||
@ -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 <npm-spec>`. 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/<name>/` 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
|
||||
|
||||
<Steps>
|
||||
<Step title="Create the package">
|
||||
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.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Define the entry point">
|
||||
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.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Import from focused subpaths">
|
||||
Always import from specific `openclaw/plugin-sdk/<subpath>` paths rather than
|
||||
the monolithic root. The old `openclaw/plugin-sdk/compat` barrel is deprecated
|
||||
(see [SDK Migration](/plugins/sdk-migration)).
|
||||
<Step title="Import from focused SDK subpaths">
|
||||
Always import from specific `openclaw/plugin-sdk/\<subpath\>` 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/
|
||||
<Accordion title="Common subpaths reference">
|
||||
| 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 |
|
||||
</Accordion>
|
||||
|
||||
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.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Use local barrels for internal imports">
|
||||
Within your extension, create barrel files for internal code sharing instead
|
||||
of importing through the plugin SDK:
|
||||
<Step title="Use local modules for internal imports">
|
||||
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";
|
||||
```
|
||||
|
||||
<Warning>
|
||||
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.
|
||||
</Warning>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Add a plugin manifest">
|
||||
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.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Test with contract tests">
|
||||
OpenClaw runs contract tests against all registered plugins. After adding your
|
||||
extension, run:
|
||||
<Step title="Test your plugin">
|
||||
**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";
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Publish and install">
|
||||
**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 <query>
|
||||
openclaw plugins install <npm-spec>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## 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/<name>` subpath
|
||||
2. **No direct src/ imports** — plugins cannot import `../../src/` directly
|
||||
3. **No self-imports** — plugins cannot import their own `plugin-sdk/\<name\>` 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
|
||||
|
||||
<Check>**package.json** has correct `openclaw` metadata</Check>
|
||||
<Check>Entry point uses `defineChannelPluginEntry` or `definePluginEntry`</Check>
|
||||
<Check>All imports use focused `plugin-sdk/<subpath>` paths</Check>
|
||||
<Check>Internal imports use local barrels, not SDK self-imports</Check>
|
||||
<Check>All imports use focused `plugin-sdk/\<subpath\>` paths</Check>
|
||||
<Check>Internal imports use local modules, not SDK self-imports</Check>
|
||||
<Check>`openclaw.plugin.json` manifest is present and valid</Check>
|
||||
<Check>Contract tests pass (`pnpm test:contracts`)</Check>
|
||||
<Check>Unit tests colocated as `*.test.ts`</Check>
|
||||
<Check>`pnpm check` passes (lint + format)</Check>
|
||||
<Check>Doc page created under `docs/channels/` or `docs/plugins/`</Check>
|
||||
<Check>Tests pass</Check>
|
||||
<Check>`pnpm check` passes (in-repo plugins)</Check>
|
||||
|
||||
## 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
|
||||
|
||||
@ -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/\<subpath\> 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user