api.on("hook_name", handler) and return the result documented for that hook.
There are three different hook systems:
Plugins can also register internal hooks with
api.registerHook(...). That is
not the typed API: registering an underscore name such as before_tool_call
there produces a warning, and the typed runner never invokes that registration.
Use api.on(...) for every hook in the hook
catalog.
Quick start
This example replies to a user message containinghook-demo-check without
calling the model.
It assumes you already have a working Gateway and can send it a normal chat
message. For package metadata, publishing, and install options, see
Building plugins and Plugin manifest.
Create a local hook-demo directory with these files:
package.json
openclaw.plugin.json
index.ts
--force acknowledges installing from
a local source):
openclaw.json:
hook-demo-check as a normal chat message. Expect Hook is working.; other
messages continue through the normal agent path. If the hook does not run,
see Troubleshooting.
Despite its name, cleanedBody is the prepared run prompt and can contain
channel context. The example matches a distinctive marker instead of assuming
the field is only the sender’s raw text.
Permissions and scope
Hook registration does not bypass plugin loading rules. The plugin must be loaded and enabled;plugins.enabled, plugins.allow, and plugins.deny still
apply. Run openclaw plugins reload <id> after changing plugin code. With the default hybrid
reload mode, hook policy changes hot-reload the existing plugin runtime.
- Non-bundled plugins need explicit
plugins.entries.<id>.hooks.allowConversationAccess: trueforbefore_model_resolve,agent_turn_prepare,before_prompt_build,before_agent_reply,llm_input,llm_output,before_agent_finalize,agent_end, andbefore_agent_run. Bundled plugins are allowed unless this option is explicitlyfalse. allowPromptInjection: falseblocksagent_turn_prepare,before_prompt_build,heartbeat_prompt_contribution, and durable next-turn injections. It defaults to allowed, but does not grant conversation access. The first two hooks therefore need both permissions.- These are specific registration gates, not a sandbox or a universal filter for every hook that can see message data. Install only plugins you trust.
(event, ctx). The event describes the operation;
the second argument carries hook-specific context. Fields such as
ctx.agentId, ctx.sessionKey, and ctx.runId are optional on many hooks and
may be absent for the emitting path. A registration is not automatically
scoped to one agent or session: check the context in your handler when needed.
Read your plugin’s resolved settings from api.pluginConfig inside the
registration closure. Typed hooks do not receive a universal
event.context.pluginConfig field; that field belongs to the internal
api.registerHook(...) event contract.
By default in hybrid reload mode, editing plugins.entries.<id>.config replaces the
plugin instance and reruns registration with the new settings.
Choose a hook
The catalog is the registration API, not a promise that every runtime emits
every hook. For example,
before_agent_run is implemented by the embedded and
CLI runners; do not rely on it as a Codex or Copilot input gate. Native tool,
transcript, and compaction boundaries also differ. See
Codex hook boundaries and
Agent harness plugins.
Troubleshooting
Upcoming deprecations
A few hook-adjacent surfaces are deprecated but still supported. Removal eligibility is tracked per surface in the plugin compatibility registry, as aremoveAfter date or an explicit removal gate, not at a major-version
boundary. Migrate now:
- Plaintext channel envelopes in
inbound_claimandmessage_receivedhandlers. Prefer typed fields instead of parsing flat envelope text:inbound_claimexposesevent.bodyForAgent;message_receivedexposesevent.contentand structured metadata, not aBodyForAgentfield. See Plaintext channel envelopes → BodyForAgent. onResolutioninbefore_tool_callnow uses the typedPluginApprovalResolutionunion (allow-once/allow-always/deny/timeout/cancelled) instead of a free-formstring.api.registerSessionExtension/api.enqueueNextTurnInjectionremain as top-level compatibility aliases. New plugins should useapi.session.state.registerSessionExtension(...)andapi.session.workflow.enqueueNextTurnInjection(...).
command-auth → command-status rename - see
Plugin SDK migration → Active deprecations.
Where each section moved
Every section of the single-page version now lives on this page or on one of the five child pages below. The anchors from the single-page version still resolve here.Hook reference
Hook reference — Registration rules, execution contracts, per-handler budgets, and the complete typed hook catalog.Tool call policy hooks
Tool call policy hooks — Parameter rewrites, blocks, approvals, exec environment contributions, and transcript persistence.Prompt and session hooks
Prompt and session hooks — Model resolution, prompt construction, finalization, and durable plugin-owned session state.- Debug runtime hooks
- Prompt and model hooks
- Authorized prompt enrichment
- Session extensions and next-turn injections
Message and delivery hooks
Message and delivery hooks — Inbound interception, reply takeover, and outbound delivery policy.Gateway and install lifecycle hooks
Gateway and install lifecycle hooks — Install-time checks, Gateway service lifecycle, and safe external cron projection.Related
- Plugin SDK migration - active deprecations and removal timeline
- Building plugins
- Plugin SDK overview
- Plugin entry points
- Internal hooks
- Webhooks
- Plugin architecture internals