# OpenClaw Plugin Development When creating or modifying an OpenClaw plugin. > Reference docs in `{baseDir}/docs/` cover each topic in detail. ## Process ### 1. Scaffold Create the project structure: ``` my-plugin/ plugin/ index.ts # export default { id, name, register } openclaw.plugin.json # config schema (additionalProperties: false) package.json # name, version, type: module hooks/ # one file per hook handler tools/ # tool registrations core/ # pure logic (no plugin-sdk imports) scripts/ install.mjs # --install / --uninstall package.json # dev dependencies tsconfig.plugin.json .gitignore ``` See `{baseDir}/docs/structure.md` for conventions. ### 2. Entry Point Follow the globalThis lifecycle pattern: ```typescript const _G = globalThis as Record; const LIFECYCLE_KEY = "_myPluginLifecycleRegistered"; export default { id: "my-plugin", name: "My Plugin", register(api) { if (!_G[LIFECYCLE_KEY]) { _G[LIFECYCLE_KEY] = true; // gateway-level init (once) api.on("gateway_stop", () => { _G[LIFECYCLE_KEY] = false; }); } // agent-level hooks (every register call, dedup inside) registerMyHook(api); registerMyTools(api); }, }; ``` See `{baseDir}/docs/entry-point.md` for details. ### 3. Hooks Each hook in its own file under `hooks/`. Must use dedup on globalThis: - `before_model_resolve`, `before_prompt_build` → WeakSet on event object - `agent_end` → Set on runId with size cap 500 - `gateway_start/stop` → globalThis flag If returning `prependSystemContext`/`appendSystemContext`, set `allowPromptInjection: true` in config. See `{baseDir}/docs/hooks.md`. ### 4. Tools Interface is `inputSchema` + `execute` (not `parameters` + `handler`): ```typescript api.registerTool({ name: "my-tool", inputSchema: { type: "object", properties: { ... }, required: [...] }, execute: async (toolCallId, params) => { return { result: "ok" }; }, }); ``` For agent context access, use factory form: `api.registerTool((ctx) => ({ ... }))`. See `{baseDir}/docs/tools.md`. ### 5. State Management All mutable state on `globalThis`, not module-level variables (hot reload resets modules). - Business state, dedup sets, lifecycle flags → `globalThis` - Cross-plugin API → `globalThis.__pluginId` - Pure functions → module-level is fine See `{baseDir}/docs/state.md`. ### 6. Config Schema `openclaw.plugin.json` must have `additionalProperties: false`. Every config field in install script must exist in schema. Never set sensitive fields (tokens) in install script. See `{baseDir}/docs/config.md`. ### 7. Install Script ```bash node scripts/install.mjs --install # build, copy to ~/.openclaw/plugins/, update config node scripts/install.mjs --uninstall # remove from config and filesystem ``` See `{baseDir}/docs/config.md`. ### 8. Build & Test ```bash npm run build node scripts/install.mjs --install openclaw gateway restart openclaw logs --follow ``` ### 9. Checklist Before Deploy - [ ] Hook handlers have dedup on globalThis - [ ] Gateway lifecycle protected by globalThis flag - [ ] Business state on globalThis - [ ] Schema matches actual config fields - [ ] Install script uses setIfMissing, no sensitive fields - [ ] Clean dist before copy (`rmSync`) See `{baseDir}/docs/debugging.md` for full checklist.