Most channel plugins do not need approval-specific code. Reach for this page when yours does. Part of the Building channel plugins guide.

Approvals and channel capabilities

Most channel plugins do not need approval-specific code. Core owns same-chat /approve, shared approval button payloads, and generic fallback delivery. ChannelPlugin.approvals was removed; put approval delivery/native/render/auth facts on one approvalCapability object instead. plugin.auth is login/logout only - core no longer reads approval auth hooks from that object.

Use approvalCapability.delivery only for native approval routing or fallback suppression, and approvalCapability.render only when a channel truly needs custom approval payloads instead of the shared renderer. delivery.shouldBlockForwardingFallback rejects a fallback that cannot enforce the selected reviewer policy even when no native handler is running. A channel may require native reviewer delivery and reject all generic forwarding for a selected policy; delivery.shouldSuppressForwardingFallback avoids duplicate delivery only while the native handler is active. Both receive the approval request payload. For a terminal notice without a cached pending request, core reconstructs it from the resolved event with createdAtMs and expiresAtMs set to zero; policy checks should use the nested request payload.

Approval auth

Payload lifecycle and setup guidance

Native approval delivery

If a channel needs native approval delivery, keep channel code focused on target normalization plus transport/presentation facts. Use createChannelExecApprovalProfile, createChannelNativeOriginTargetResolver, createChannelApproverDmTargetResolver, and createApproverRestrictedNativeApprovalCapability from apus/plugin-sdk/approval-runtime. Put the channel-specific facts behind approvalCapability.nativeRuntime, ideally via createChannelApprovalNativeRuntimeAdapter(...) or createLazyChannelApprovalNativeRuntimeAdapter(...), so core can assemble the handler and own request filtering, routing, dedupe, expiry, gateway subscription, and routed-elsewhere notices.

nativeRuntime is split into a few smaller seams:

Native approval runtimes can receive three approval kinds: exec, plugin, and system-agent. A system-agent request asks an operator to approve a Gateway-side persistent change, such as a config write or Gateway restart. The runtime must render the typed approval actions and then render the final application result. An allowed request can finish as applied or not applied; do not treat the recorded approval alone as proof that the change completed.

Reaction bindings must preserve the explicit approval kind from delivery through decision resolution, including after loading persisted plugin state. iMessage native poll and reaction indexes follow the same rule after a restart.

Other approval helpers:

Narrower approval runtime subpaths

For hot channel entrypoints, prefer these narrower subpaths over the broader approval-runtime barrel when you only need one part of that family:

Likewise, prefer apus/plugin-sdk/reply-runtime, apus/plugin-sdk/reply-dispatch-runtime, apus/plugin-sdk/reply-reference, and apus/plugin-sdk/reply-chunking over broader umbrella surfaces when you do not need them all.