* docs(plugins): add SDK reference and how-to guide pages Create 7 new plugin SDK documentation pages: - sdk-overview: import map, registration API reference - sdk-entrypoints: definePluginEntry/defineChannelPluginEntry reference - sdk-runtime: api.runtime namespace reference - sdk-setup: packaging, manifests, config schemas reference - sdk-channel-plugins: step-by-step channel plugin how-to - sdk-provider-plugins: step-by-step provider plugin how-to - sdk-testing: test utilities and patterns reference Restructure plugin docs navigation with nested groups: - Top-level: user-facing pages (Install, Community, Bundles) - Building Plugins: Getting Started, Channel, Provider - SDK Reference: Overview, Entry Points, Runtime, Setup, Testing, Migration, Manifest, Internals Revise existing pages for new IA: - building-plugins.md: tightened as quick-start, routes to detailed guides - architecture.md: updated info box with links to new guides - sdk-migration.md: expanded Related section * docs(plugins): add Mintlify components (Steps, CodeGroup, Tabs, Accordion, CardGroup) - Channel plugin guide: wrap walkthrough in Steps, use CodeGroup for package.json/manifest, Accordion for createChatChannelPlugin details, CardGroup for advanced topics - Provider plugin guide: wrap walkthrough in Steps, use CodeGroup for package files, Tabs for hook examples, Accordion for all-hooks reference - Getting started: use CardGroup for plugin-type picker and next steps, CodeGroup for package/manifest - SDK Overview: wrap subpath tables in AccordionGroup for scannability * fix(docs): address PR review feedback on plugin SDK pages - Remove nonexistent api.runtime.channel.handleInboundMessage call, replace with realistic webhook pattern and note about channel-specific inbound handling (issue a) - Fix registrationMode values: 'setup' -> 'setup-only' and 'setup-runtime' matching actual PluginRegistrationMode type (issue b) - Fix createOptionalChannelSetupSurface params: channelId -> channel, add required label field (issue c) - Fix broken anchor links: #multi-capability-providers -> #step-5-add-extra-capabilities, #plugin-kinds -> #registration-api (issue d) - Add missing acmeChatApi import in channel plugin example (issue e) - Fix undefined provider variable in provider test example (issue f) * fix(docs): use correct createProviderApiKeyAuthMethod options Replace incorrect params (provider, validate) with actual required fields (providerId, methodId, optionKey, flagName, promptMessage) matching src/plugins/provider-api-key-auth.ts. * fix(docs): address second round of PR review feedback - Add required model fields (reasoning, input, cost, contextWindow, maxTokens) to catalog example (issue b) - Fix buildChannelConfigSchema to take a Zod schema argument (issue c) - Replace fabricated setupWizard steps/run with real ChannelSetupWizard contract (channel, status, credentials) (issue d) - Add required sessionFile/workspaceDir to runEmbeddedPiAgent (issue e) - Fix wrapStreamFn to return StreamFn from ctx.streamFn (issue f)
8.4 KiB
title, sidebarTitle, summary, read_when
| title | sidebarTitle | summary | read_when | |||
|---|---|---|---|---|---|---|
| Building Plugins | Getting Started | Create your first OpenClaw plugin in minutes |
|
Building Plugins
Plugins extend OpenClaw with new capabilities: channels, model providers, speech, image generation, web search, agent tools, or any combination.
You do not need to add your plugin to the OpenClaw repository. Publish on npm
and users install with openclaw plugins install <npm-spec>.
Prerequisites
- Node >= 22 and a package manager (npm or pnpm)
- Familiarity with TypeScript (ESM)
- For in-repo plugins: repository cloned and
pnpm installdone
What kind of plugin?
Connect OpenClaw to a messaging platform (Discord, IRC, etc.) Add a model provider (LLM, proxy, or custom endpoint) Register agent tools, event hooks, or services — continue belowQuick start: tool plugin
This walkthrough creates a minimal plugin that registers an agent tool. Channel and provider plugins have dedicated guides linked above.
```json package.json { "name": "@myorg/openclaw-my-plugin", "version": "1.0.0", "type": "module", "openclaw": { "extensions": ["./index.ts"] } } ``````json openclaw.plugin.json
{
"id": "my-plugin",
"name": "My Plugin",
"description": "Adds a custom tool to OpenClaw",
"configSchema": {
"type": "object",
"additionalProperties": false
}
}
```
</CodeGroup>
Every plugin needs a manifest, even with no config. See
[Manifest](/plugins/manifest) for the full schema.
```typescript
// index.ts
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { Type } from "@sinclair/typebox";
export default definePluginEntry({
id: "my-plugin",
name: "My Plugin",
description: "Adds a custom tool to OpenClaw",
register(api) {
api.registerTool({
name: "my_tool",
description: "Do a thing",
parameters: Type.Object({ input: Type.String() }),
async execute(_id, params) {
return { content: [{ type: "text", text: `Got: ${params.input}` }] };
},
});
},
});
```
`definePluginEntry` is for non-channel plugins. For channels, use
`defineChannelPluginEntry` — see [Channel Plugins](/plugins/sdk-channel-plugins).
For full entry point options, see [Entry Points](/plugins/sdk-entrypoints).
**External plugins:**
```bash
npm publish
openclaw plugins install @myorg/openclaw-my-plugin
```
**In-repo plugins:** place under `extensions/` — automatically discovered.
```bash
pnpm test -- extensions/my-plugin/
```
Plugin capabilities
A single plugin can register any number of capabilities via the api object:
| Capability | Registration method | Detailed guide |
|---|---|---|
| Text inference (LLM) | api.registerProvider(...) |
Provider Plugins |
| Channel / messaging | api.registerChannel(...) |
Channel Plugins |
| Speech (TTS/STT) | api.registerSpeechProvider(...) |
Provider Plugins |
| Media understanding | api.registerMediaUnderstandingProvider(...) |
Provider Plugins |
| Image generation | api.registerImageGenerationProvider(...) |
Provider Plugins |
| Web search | api.registerWebSearchProvider(...) |
Provider Plugins |
| Agent tools | api.registerTool(...) |
Below |
| Custom commands | api.registerCommand(...) |
Entry Points |
| Event hooks | api.registerHook(...) |
Entry Points |
| HTTP routes | api.registerHttpRoute(...) |
Internals |
| CLI subcommands | api.registerCli(...) |
Entry Points |
For the full registration API, see SDK Overview.
Registering agent tools
Tools are typed functions the LLM can call. They can be required (always available) or optional (user opt-in):
register(api) {
// Required tool — always available
api.registerTool({
name: "my_tool",
description: "Do a thing",
parameters: Type.Object({ input: Type.String() }),
async execute(_id, params) {
return { content: [{ type: "text", text: params.input }] };
},
});
// Optional tool — user must add to allowlist
api.registerTool(
{
name: "workflow_tool",
description: "Run a workflow",
parameters: Type.Object({ pipeline: Type.String() }),
async execute(_id, params) {
return { content: [{ type: "text", text: params.pipeline }] };
},
},
{ optional: true },
);
}
Users enable optional tools in config:
{
tools: { allow: ["workflow_tool"] },
}
- Tool names must not clash with core tools (conflicts are skipped)
- Use
optional: truefor tools with side effects or extra binary requirements - Users can enable all tools from a plugin by adding the plugin id to
tools.allow
Import conventions
Always import from focused openclaw/plugin-sdk/<subpath> paths:
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
// Wrong: monolithic root (deprecated, will be removed)
import { ... } from "openclaw/plugin-sdk";
For the full subpath reference, see SDK Overview.
Within your plugin, use local barrel files (api.ts, runtime-api.ts) for
internal imports — never import your own plugin through its SDK path.
Pre-submission checklist
package.json has correct openclaw metadata
openclaw.plugin.json manifest is present and valid
Entry point uses defineChannelPluginEntry or definePluginEntry
All imports use focused plugin-sdk/<subpath> paths
Internal imports use local modules, not SDK self-imports
Tests pass (pnpm test -- extensions/my-plugin/)
pnpm check passes (in-repo plugins)