The 3 types
before — before processing
after — after processing
onError — when something fails
bot.hook(event, phase, handler, priority?) if you
need to register a phase dynamically instead of calling before/after/
onError directly.
A handler error is always emitted on bot.on('error', ({ error, ctx }) => ...),
whether or not an onError('message') hook is registered.
Use cases
Logging
logIds.
Rate limiting
Filter groups
groupMode option is usually
simpler: it filters before the hooks even run.
Block users
bot.blacklist / bot.whitelist:
they drop the message before it is even persisted and also cover button
taps and calls. See Handoff & access lists.
Transform or block outgoing messages
'send' fires on every outbound send, whether it comes from
ctx.reply/ctx.photo/… or from bot.send. The payload
is { chatId, content, options } — content is the transport-neutral
OutboundContent ({ kind: 'text', text }, { kind: 'image', media, caption }, …); mutate it to transform a message, or abort to skip the
send entirely:
{ skipped: true } instead of rejecting (a
broadcast counts it as skipped).
Edits and streamed output
Some payloads carry a marker so a hook can tell them apart from a plain send:
A hook can rewrite
payload.content or abort() any of them. An aborted edit
resolves skipped and sends no new message in its place. A degraded edit (the
provider cannot edit) goes out as a normal send, without edit.
abort() — stop the pipeline
abort() stops the remaining before hooks and the event itself. Returning
without next() has the same effect; abort() also works from code that has
no next at hand (a helper that receives only ctx).
Priority
Typed contexts
The context a hook receives is typed by the event name (HookContextMap):
'message' gives a MessageContext, 'send' a SendHookContext
({ chatId, content, options?, stream?, edit? }), 'call' a CallEvent,
'reaction' an InboundReaction, and so on; before hooks also get
abort(). No annotation is needed:
unknown unless you
annotate the handler, pass the type argument, or augment HookContextMap
(declaration merging):
HookHandler<TCtx = unknown> and HookErrorHandler<TCtx = unknown> name a
handler’s type; a handler typed for one event (HookHandler<MessageContext>)
cannot be registered on another ('send').
Available events
Provider-level connection events (
qr, ready, connection, …) are
plain bot.on(...) listeners, not hook phases — see Core concepts.
The same goes for 'conversation:started' / 'conversation:ended'
(see Operations).
Next steps
- Plugins — package hooks for reuse
- Example: rate limiter — full plugin