# create-plugin > Build or upgrade OpenClaw plugins/extensions end-to-end from one request. Use when asked to create, modify, scaffold, debug, test, or enable OpenClaw plugins, including plugin commands, agent tools, gateway methods, services, channels, provider-auth flows, plugin manifests, plugin config schemas/uiHints, and plugin allowlist wiring. - Author: Max Schulze-Melander - Repository: m3lander/skills - Version: 20260207014722 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-07 - Source: https://github.com/m3lander/skills - Web: https://mule.run/skillshub/@@m3lander/skills~create-plugin:20260207014722 --- --- name: create-plugin description: Build or upgrade OpenClaw plugins/extensions end-to-end from one request. Use when asked to create, modify, scaffold, debug, test, or enable OpenClaw plugins, including plugin commands, agent tools, gateway methods, services, channels, provider-auth flows, plugin manifests, plugin config schemas/uiHints, and plugin allowlist wiring. --- # Create OpenClaw Plugin Execute a full plugin delivery in one run: scaffold, implement, configure, validate, test, enable, and troubleshoot. ## Load Order (progressive disclosure) 1. Read `references/source-map.md` first. 2. Read `references/plugin-architecture.md` before writing any plugin files. 3. Read one or more targeted files based on request scope: - `references/templates.md` for concrete code templates. - `references/patterns-example-plugins.md` for proven patterns from official plugins. - `references/testing-and-validation.md` for test and health checks. - `references/troubleshooting-playbook.md` for failure handling. - `references/pr-3160-analysis.md` for reliability and hardening patterns. ## Workflow 1. Parse request into a plugin contract. - Extract plugin kind: command/tool/service/channel/provider/hook/CLI combination. - Extract side effects, external dependencies, auth requirements, expected inputs/outputs, latency constraints. - Extract required config keys and defaults. - Record unresolved items as explicit assumptions; do not block unless implementation is impossible. 2. Resolve plugin id and location. - Normalize id to lowercase hyphen-case. - Prefer workspace-local development path: `/.openclaw/extensions/`. - Use global path only when explicitly requested: `~/.openclaw/extensions/`. - If packaging as npm plugin, keep `package.json` `openclaw.extensions` aligned with entry file(s). 3. Scaffold deterministically. - Run `scripts/scaffold-plugin.sh --id --root --mode `. - Modes: `basic`, `tool-service`, `channel`, `provider`. - Generate at minimum: `openclaw.plugin.json`, `index.ts`, optional `package.json`. 4. Author the manifest (`openclaw.plugin.json`). - Always set `id` and `configSchema`. - Keep `configSchema` strict (`type: object`, `additionalProperties: false`). - Add optional fields only when used: `kind`, `channels`, `providers`, `skills`, `name`, `description`, `version`, `uiHints`. - For secrets, mark corresponding `uiHints` entries with `sensitive: true`. 5. Implement entrypoint registration (`index.ts`). - Export a plugin object with `id`, `name`, `description`, `configSchema`, `register(api)`. - Keep registration synchronous; do not rely on async `register` completion. - Move expensive initialization into lazy runtime builders used by handlers/tools/services. 6. Implement requested surfaces. - Command: `api.registerCommand(...)` with strict argument handling and auth policy. - Tool: `api.registerTool(...)`; use `optional: true` for side-effectful or dependency-heavy tools. - Gateway RPC: `api.registerGatewayMethod(".", ...)`. - CLI: `api.registerCli(...)` and declare `commands` metadata. - Service: `api.registerService({ id, start, stop })` for lifecycle-managed runtime. - Channel plugin: `api.registerChannel({ plugin })` and keep channel config under `channels.`. - Provider auth plugin: `api.registerProvider(...)` returning `profiles`, optional `configPatch`, optional `defaultModel`. 7. Wire optional tools and allowlists. - Optional plugin tools are disabled by default. - Enable with `tools.allow` / `agents.list[].tools.allow` using one of: - tool name - plugin id - `group:plugins` - Avoid tool-name collisions with core tools. - Avoid plugin-id collisions with core tool names. 8. Configure and enable. - Add config at `plugins.entries..config` unless feature uses dedicated top-level config (for example channels). - Enable plugin via `openclaw plugins enable ` or config toggle `plugins.entries..enabled = true`. - Restart gateway after plugin or config changes. - Run health checks: `openclaw plugins list`, `openclaw plugins info `, `openclaw plugins doctor`. 9. Validate and test. - Run `scripts/validate-plugin.sh ` for static checks. - Add/execute tests per `references/testing-and-validation.md`: - Unit: config parsing, validation, handler behavior, path safety. - Integration: plugin load + registration + runtime surfaces. - Manual smoke: enable, restart, exercise feature, inspect doctor output. 10. Apply hardening guardrails. - Add timeouts around subprocess/network boundaries. - Validate and sanitize untrusted inputs. - Enforce path boundaries (no traversal, no symlink escapes). - Add fallback behavior where external dependencies can fail. - Log actionable errors without leaking secrets. 11. Deliver output bundle. - Provide file tree. - Provide exact file contents. - Provide required config snippets. - Provide test commands executed and outcomes. - Provide assumptions and unresolved risks. ## Copy/Paste Templates ### Template A: Tool + Service with lazy runtime ```ts import { Type } from "@sinclair/typebox"; const configSchema = { parse(value: unknown) { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; return { enabled: raw.enabled !== false, timeoutMs: typeof raw.timeoutMs === "number" ? raw.timeoutMs : 5000, }; }, uiHints: { timeoutMs: { label: "Timeout (ms)", advanced: true }, }, }; export default { id: "plugin-id", name: "Plugin Name", description: "One-line summary", configSchema, register(api) { const cfg = configSchema.parse(api.pluginConfig); let runtimePromise: Promise<{ ping: () => Promise }> | null = null; const ensureRuntime = async () => { if (!cfg.enabled) { throw new Error("plugin disabled"); } if (!runtimePromise) { runtimePromise = Promise.resolve({ ping: async () => "ok" }); } return await runtimePromise; }; api.registerTool({ name: "plugin_ping", description: "Health check", parameters: Type.Object({}), async execute() { try { const rt = await ensureRuntime(); return { content: [{ type: "text", text: await rt.ping() }] }; } catch (err) { return { content: [{ type: "text", text: String(err) }] }; } }, }); api.registerService({ id: "plugin-id", start: async () => { await ensureRuntime(); }, stop: async () => { runtimePromise = null; }, }); }, }; ``` ### Template B: Channel plugin wrapper ```ts import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; import { channelPlugin } from "./src/channel.js"; export default { id: "my-channel", name: "My Channel", description: "Channel integration", configSchema: emptyPluginConfigSchema(), register(api) { api.registerChannel({ plugin: channelPlugin }); }, }; ``` ### Template C: Provider OAuth plugin ```ts import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; export default { id: "my-provider-auth", name: "My Provider Auth", description: "Provider auth flow", configSchema: emptyPluginConfigSchema(), register(api) { api.registerProvider({ id: "my-provider", label: "My Provider", auth: [ { id: "oauth", label: "OAuth", kind: "oauth", run: async () => ({ profiles: [ { profileId: "my-provider:default", credential: { type: "oauth", provider: "my-provider", access: "", refresh: "", expires: Date.now() + 3600_000, }, }, ], defaultModel: "my-provider/default-model", }), }, ], }); }, }; ``` ## Advanced End-to-End Examples (derived from official plugins) 1. `memory-core` pattern. - Registers tool factory returning multiple tools (`memory_search`, `memory_get`) with `names` metadata. - Registers plugin-owned CLI surface. - Uses `kind: "memory"` slot behavior. 2. `discord` pattern. - Uses thin entrypoint and channel module composition. - Registers channel plugin with onboarding/pairing/security/groups/resolver/status adapters. - Keeps plugin config empty and stores channel settings under `channels.discord`. 3. `voice-call` pattern. - Combines gateway RPC methods + agent tool + CLI + background service. - Uses lazy runtime initialization and provider-specific validation. - Uses strict config schema + `uiHints` for Control UI. - Uses defensive error handling with actionable messages. 4. `google-antigravity-auth` pattern. - Registers provider auth flow with OAuth callback/manual fallback. - Returns profiles, config patch, default model, and operator notes. - Keeps plugin config schema empty and pushes auth state into auth profiles. Use `references/patterns-example-plugins.md` for implementation details and anti-patterns.