TaskFlow
DashboardFreewriteWhiteboardsProjectsCRMTasksNotificationsSettingsAgent TowerAPI Docs
OpenClaw Docs
?

User

Member

Caricamento in corso...

Home
Progetti
Task
Notifiche
CRM

    OpenClaw

    Documentation Mirror

    Documentation Overview

    Docs

    Auth credential semantics
    Scheduled tasks
    Hooks
    Automation & tasks
    Standing orders
    Task flow
    Background tasks
    BlueBubbles
    Broadcast groups
    Channel routing
    Discord
    Feishu
    Google Chat
    Group messages
    Groups
    iMessage
    Chat channels
    IRC
    LINE
    Channel location parsing
    Matrix
    Matrix migration
    Matrix push rules for quiet previews
    Mattermost
    Microsoft Teams
    Nextcloud Talk
    Nostr
    Pairing
    QA channel
    QQ bot
    Signal
    Slack
    Synology Chat
    Telegram
    Tlon
    Channel troubleshooting
    Twitch
    WeChat
    WhatsApp
    Yuanbao
    Zalo
    Zalo personal
    CI pipeline
    ACP
    Agent
    Agents
    Approvals
    Backup
    Browser
    Channels
    Clawbot
    `openclaw commitments`
    Completion
    Config
    Configure
    Cron
    Daemon
    Dashboard
    Devices
    Directory
    DNS
    Docs
    Doctor
    Flows (redirect)
    Gateway
    Health
    Hooks
    CLI reference
    Inference CLI
    Logs
    MCP
    Memory
    Message
    Migrate
    Models
    Node
    Nodes
    Onboard
    Pairing
    Plugins
    Proxy
    QR
    Reset
    Sandbox CLI
    Secrets
    Security
    Sessions
    Setup
    Skills
    Status
    System
    `openclaw tasks`
    TUI
    Uninstall
    Update
    Voicecall
    Webhooks
    Wiki
    Active memory
    Agent runtime
    Agent loop
    Agent runtimes
    Agent workspace
    Gateway architecture
    Channel docking
    Inferred commitments
    Compaction
    Context
    Context engine
    Delegate architecture
    Dreaming
    Experimental features
    Features
    Markdown formatting
    Memory overview
    Builtin memory engine
    Honcho memory
    QMD memory engine
    Memory search
    Messages
    Model failover
    Model providers
    Models CLI
    Multi-agent routing
    OAuth
    OpenClaw App SDK
    Presence
    QA overview
    Matrix QA
    Command queue
    Steering queue
    Retry policy
    Session management
    Session pruning
    Session tools
    SOUL.md personality guide
    Streaming and chunking
    System prompt
    Timezones
    TypeBox
    Typing indicators
    Usage tracking
    Date and time
    Node + tsx crash
    Diagnostics flags
    Authentication
    Background exec and process tool
    Bonjour discovery
    Bridge protocol
    CLI backends
    Configuration — agents
    Configuration — channels
    Configuration — tools and custom providers
    Configuration
    Configuration examples
    Configuration reference
    Diagnostics export
    Discovery and transports
    Doctor
    Gateway lock
    Health checks
    Heartbeat
    Gateway runbook
    Local models
    Gateway logging
    Multiple gateways
    Network model
    OpenAI chat completions
    OpenResponses API
    OpenShell
    OpenTelemetry export
    Gateway-owned pairing
    Prometheus metrics
    Gateway protocol
    Remote access
    Remote gateway setup
    Sandbox vs tool policy vs elevated
    Sandboxing
    Secrets management
    Secrets apply plan contract
    Security audit checks
    Security
    Tailscale
    Tools invoke API
    Troubleshooting
    Trusted proxy auth
    Debugging
    Environment variables
    FAQ
    FAQ: first-run setup
    FAQ: models and auth
    GPT-5.5 / Codex agentic parity
    GPT-5.5 / Codex parity maintainer notes
    Help
    Scripts
    Testing
    Testing: live suites
    General troubleshooting
    OpenClaw
    Ansible
    Azure
    Bun (experimental)
    ClawDock
    Release channels
    DigitalOcean
    Docker
    Docker VM runtime
    exe.dev
    Fly.io
    GCP
    Hetzner
    Hostinger
    Install
    Installer internals
    Kubernetes
    macOS VMs
    Migration guide
    Migrating from Claude
    Migrating from Hermes
    Nix
    Node.js
    Northflank
    Oracle Cloud
    Podman
    Railway
    Raspberry Pi
    Render
    Uninstall
    Updating
    Logging
    Network
    Audio and voice notes
    Camera capture
    Image and media support
    Nodes
    Location command
    Media understanding
    Talk mode
    Node troubleshooting
    Voice wake
    Pi integration architecture
    Pi development workflow
    Android app
    Platforms
    iOS app
    Linux app
    Gateway on macOS
    Canvas
    Gateway lifecycle
    macOS dev setup
    Health checks (macOS)
    Menu bar icon
    macOS logging
    Menu bar
    Peekaboo bridge
    macOS permissions
    Remote control
    macOS signing
    Skills (macOS)
    Voice overlay
    Voice wake (macOS)
    WebChat (macOS)
    macOS IPC
    macOS app
    Windows
    Plugin internals
    Plugin architecture internals
    Building plugins
    Plugin bundles
    Codex Computer Use
    Codex harness
    Community plugins
    Plugin compatibility
    Google Meet plugin
    Plugin hooks
    Plugin manifest
    Memory LanceDB
    Memory wiki
    Message presentation
    Agent harness plugins
    Building channel plugins
    Channel turn kernel
    Plugin entry points
    Plugin SDK migration
    Plugin SDK overview
    Building provider plugins
    Plugin runtime helpers
    Plugin setup and config
    Plugin SDK subpaths
    Plugin testing
    Skill workshop plugin
    Voice call plugin
    Webhooks plugin
    Zalo personal plugin
    OpenProse
    Alibaba Model Studio
    Anthropic
    Arcee AI
    Azure Speech
    Amazon Bedrock
    Amazon Bedrock Mantle
    Chutes
    Claude Max API proxy
    Cloudflare AI gateway
    ComfyUI
    Deepgram
    Deepinfra
    DeepSeek
    ElevenLabs
    Fal
    Fireworks
    GitHub Copilot
    GLM (Zhipu)
    Google (Gemini)
    Gradium
    Groq
    Hugging Face (inference)
    Provider directory
    Inferrs
    Inworld
    Kilocode
    LiteLLM
    LM Studio
    MiniMax
    Mistral
    Model provider quickstart
    Moonshot AI
    NVIDIA
    Ollama
    OpenAI
    OpenCode
    OpenCode Go
    OpenRouter
    Perplexity
    Qianfan
    Qwen
    Runway
    SGLang
    StepFun
    Synthetic
    Tencent Cloud (TokenHub)
    Together AI
    Venice AI
    Vercel AI gateway
    vLLM
    Volcengine (Doubao)
    Vydra
    xAI
    Xiaomi MiMo
    Z.AI
    Default AGENTS.md
    Release policy
    API usage and costs
    Credits
    Device model database
    Full release validation
    Memory configuration reference
    OpenClaw App SDK API design
    Prompt caching
    Rich output protocol
    RPC adapters
    SecretRef credential surface
    Session management deep dive
    AGENTS.md template
    BOOT.md template
    BOOTSTRAP.md template
    HEARTBEAT.md template
    IDENTITY template
    SOUL.md template
    TOOLS.md template
    USER template
    Tests
    Token use and costs
    Transcript hygiene
    Onboarding reference
    Contributing to the threat model
    Threat model (MITRE ATLAS)
    Formal verification (security models)
    Network proxy
    Agent bootstrapping
    Docs directory
    Getting started
    Docs hubs
    OpenClaw lore
    Onboarding (macOS app)
    Onboarding overview
    Personal assistant setup
    Setup
    Showcase
    Onboarding (CLI)
    CLI automation
    CLI setup reference
    ACP agents
    ACP agents — setup
    Agent send
    apply_patch tool
    Brave search
    Browser (OpenClaw-managed)
    Browser control API
    Browser troubleshooting
    Browser login
    WSL2 + Windows + remote Chrome CDP troubleshooting
    BTW side questions
    ClawHub
    Code execution
    Creating skills
    Diffs
    DuckDuckGo search
    Elevated mode
    Exa search
    Exec tool
    Exec approvals
    Exec approvals — advanced
    Firecrawl
    Gemini search
    Grok search
    Image generation
    Tools and plugins
    Kimi search
    LLM task
    Lobster
    Tool-loop detection
    Media overview
    MiniMax search
    Multi-agent sandbox and tools
    Music generation
    Ollama web search
    PDF tool
    Perplexity search
    Plugins
    Reactions
    SearXNG search
    Skills
    Skills config
    Slash commands
    Sub-agents
    Tavily
    Thinking levels
    Tokenjuice
    Trajectory bundles
    Text-to-speech
    Video generation
    Web search
    Web fetch
    Linux server
    Control UI
    Dashboard
    Web
    TUI
    WebChat

    OpenAPI Specs

    openapi
    TaskFlow
    docs/openclaw
    Original Docs

    Real-time Synchronized Documentation

    Last sync: 01/05/2026 07:01:56

    Note: This content is mirrored from docs.openclaw.ai and is subject to their terms and conditions.

    OpenClaw Docs

    v2.4.0 Production

    Last synced: Today, 22:00

    Technical reference for the OpenClaw framework. Real-time synchronization with the official documentation engine.

    Use this file to discover all available pages before exploring further.

    Building channel plugins

    This guide walks through building a channel plugin that connects OpenClaw to a messaging platform. By the end you will have a working channel with DM security, pairing, reply threading, and outbound messaging.

    info

    If you have not built any OpenClaw plugin before, read [Getting Started](/plugins/building-plugins) first for the basic package structure and manifest setup.

    How channel plugins work

    Channel plugins do not need their own send/edit/react tools. OpenClaw keeps one shared

    text
    message
    tool in core. Your plugin owns:

    • Config — account resolution and setup wizard
    • Security — DM policy and allowlists
    • Pairing — DM approval flow
    • Session grammar — how provider-specific conversation ids map to base chats, thread ids, and parent fallbacks
    • Outbound — sending text, media, and polls to the platform
    • Threading — how replies are threaded
    • Heartbeat typing — optional typing/busy signals for heartbeat delivery targets

    Core owns the shared message tool, prompt wiring, the outer session-key shape, generic

    text
    :thread:
    bookkeeping, and dispatch.

    If your channel supports typing indicators outside inbound replies, expose

    text
    heartbeat.sendTyping(...)
    on the channel plugin. Core calls it with the resolved heartbeat delivery target before the heartbeat model run starts and uses the shared typing keepalive/cleanup lifecycle. Add
    text
    heartbeat.clearTyping(...)
    when the platform needs an explicit stop signal.

    If your channel adds message-tool params that carry media sources, expose those param names through

    text
    describeMessageTool(...).mediaSourceParams
    . Core uses that explicit list for sandbox path normalization and outbound media-access policy, so plugins do not need shared-core special cases for provider-specific avatar, attachment, or cover-image params. Prefer returning an action-keyed map such as
    text
    { "set-profile": ["avatarUrl", "avatarPath"] }
    so unrelated actions do not inherit another action's media args. A flat array still works for params that are intentionally shared across every exposed action.

    If your platform stores extra scope inside conversation ids, keep that parsing in the plugin with

    text
    messaging.resolveSessionConversation(...)
    . That is the canonical hook for mapping
    text
    rawId
    to the base conversation id, optional thread id, explicit
    text
    baseConversationId
    , and any
    text
    parentConversationCandidates
    . When you return
    text
    parentConversationCandidates
    , keep them ordered from the narrowest parent to the broadest/base conversation.

    Use

    text
    openclaw/plugin-sdk/channel-route
    when plugin code needs to normalize route-like fields, compare a child thread with its parent route, or build a stable dedupe key from
    text
    { channel, to, accountId, threadId }
    . The helper normalizes numeric thread ids the same way core does, so plugins should prefer it over ad hoc
    text
    String(threadId)
    comparisons. Plugins with provider-specific target grammar can inject their parser into
    text
    resolveChannelRouteTargetWithParser(...)
    and still get the same route target shape and thread fallback semantics core uses.

    Bundled plugins that need the same parsing before the channel registry boots can also expose a top-level

    text
    session-key-api.ts
    file with a matching
    text
    resolveSessionConversation(...)
    export. Core uses that bootstrap-safe surface only when the runtime plugin registry is not available yet.

    text
    messaging.resolveParentConversationCandidates(...)
    remains available as a legacy compatibility fallback when a plugin only needs parent fallbacks on top of the generic/raw id. If both hooks exist, core uses
    text
    resolveSessionConversation(...).parentConversationCandidates
    first and only falls back to
    text
    resolveParentConversationCandidates(...)
    when the canonical hook omits them.

    Approvals and channel capabilities

    Most channel plugins do not need approval-specific code.

    • Core owns same-chat
      text
      /approve
      , shared approval button payloads, and generic fallback delivery.
    • Prefer one
      text
      approvalCapability
      object on the channel plugin when the channel needs approval-specific behavior.
    • text
      ChannelPlugin.approvals
      is removed. Put approval delivery/native/render/auth facts on
      text
      approvalCapability
      .
    • text
      plugin.auth
      is login/logout only; core no longer reads approval auth hooks from that object.
    • text
      approvalCapability.authorizeActorAction
      and
      text
      approvalCapability.getActionAvailabilityState
      are the canonical approval-auth seam.
    • Use
      text
      approvalCapability.getActionAvailabilityState
      for same-chat approval auth availability.
    • If your channel exposes native exec approvals, use
      text
      approvalCapability.getExecInitiatingSurfaceState
      for the initiating-surface/native-client state when it differs from same-chat approval auth. Core uses that exec-specific hook to distinguish
      text
      enabled
      vs
      text
      disabled
      , decide whether the initiating channel supports native exec approvals, and include the channel in native-client fallback guidance.
      text
      createApproverRestrictedNativeApprovalCapability(...)
      fills this in for the common case.
    • Use
      text
      outbound.shouldSuppressLocalPayloadPrompt
      or
      text
      outbound.beforeDeliverPayload
      for channel-specific payload lifecycle behavior such as hiding duplicate local approval prompts or sending typing indicators before delivery.
    • Use
      text
      approvalCapability.delivery
      only for native approval routing or fallback suppression.
    • Use
      text
      approvalCapability.nativeRuntime
      for channel-owned native approval facts. Keep it lazy on hot channel entrypoints with
      text
      createLazyChannelApprovalNativeRuntimeAdapter(...)
      , which can import your runtime module on demand while still letting core assemble the approval lifecycle.
    • Use
      text
      approvalCapability.render
      only when a channel truly needs custom approval payloads instead of the shared renderer.
    • Use
      text
      approvalCapability.describeExecApprovalSetup
      when the channel wants the disabled-path reply to explain the exact config knobs needed to enable native exec approvals. The hook receives
      text
      { channel, channelLabel, accountId }
      ; named-account channels should render account-scoped paths such as
      text
      channels.<channel>.accounts.<id>.execApprovals.*
      instead of top-level defaults.
    • If a channel can infer stable owner-like DM identities from existing config, use
      text
      createResolvedApproverActionAuthAdapter
      from
      text
      openclaw/plugin-sdk/approval-runtime
      to restrict same-chat
      text
      /approve
      without adding approval-specific core logic.
    • If a channel needs native approval delivery, keep channel code focused on target normalization plus transport/presentation facts. Use
      text
      createChannelExecApprovalProfile
      ,
      text
      createChannelNativeOriginTargetResolver
      ,
      text
      createChannelApproverDmTargetResolver
      , and
      text
      createApproverRestrictedNativeApprovalCapability
      from
      text
      openclaw/plugin-sdk/approval-runtime
      . Put the channel-specific facts behind
      text
      approvalCapability.nativeRuntime
      , ideally via
      text
      createChannelApprovalNativeRuntimeAdapter(...)
      or
      text
      createLazyChannelApprovalNativeRuntimeAdapter(...)
      , so core can assemble the handler and own request filtering, routing, dedupe, expiry, gateway subscription, and routed-elsewhere notices.
      text
      nativeRuntime
      is split into a few smaller seams:
    • text
      createChannelNativeOriginTargetResolver
      uses the shared channel-route matcher by default for
      text
      { to, accountId, threadId }
      targets. Pass
      text
      targetsMatch
      only when a channel has provider-specific equivalence rules, such as Slack timestamp prefix matching.
    • Pass
      text
      normalizeTargetForMatch
      to
      text
      createChannelNativeOriginTargetResolver
      when the channel needs to canonicalize provider ids before the default route matcher or a custom
      text
      targetsMatch
      callback runs, while preserving the original target for delivery. Use
      text
      normalizeTarget
      only when the resolved delivery target itself should be canonicalized.
    • text
      availability
      — whether the account is configured and whether a request should be handled
    • text
      presentation
      — map the shared approval view model into pending/resolved/expired native payloads or final actions
    • text
      transport
      — prepare targets plus send/update/delete native approval messages
    • text
      interactions
      — optional bind/unbind/clear-action hooks for native buttons or reactions
    • text
      observe
      — optional delivery diagnostics hooks
    • If the channel needs runtime-owned objects such as a client, token, Bolt app, or webhook receiver, register them through
      text
      openclaw/plugin-sdk/channel-runtime-context
      . The generic runtime-context registry lets core bootstrap capability-driven handlers from channel startup state without adding approval-specific wrapper glue.
    • Reach for the lower-level
      text
      createChannelApprovalHandler
      or
      text
      createChannelNativeApprovalRuntime
      only when the capability-driven seam is not expressive enough yet.
    • Native approval channels must route both
      text
      accountId
      and
      text
      approvalKind
      through those helpers.
      text
      accountId
      keeps multi-account approval policy scoped to the right bot account, and
      text
      approvalKind
      keeps exec vs plugin approval behavior available to the channel without hardcoded branches in core.
    • Core now owns approval reroute notices too. Channel plugins should not send their own "approval went to DMs / another channel" follow-up messages from
      text
      createChannelNativeApprovalRuntime
      ; instead, expose accurate origin + approver-DM routing through the shared approval capability helpers and let core aggregate actual deliveries before posting any notice back to the initiating chat.
    • Preserve the delivered approval id kind end-to-end. Native clients should not guess or rewrite exec vs plugin approval routing from channel-local state.
    • Different approval kinds can intentionally expose different native surfaces. Current bundled examples:
      • Slack keeps native approval routing available for both exec and plugin ids.
      • Matrix keeps the same native DM/channel routing and reaction UX for exec and plugin approvals, while still letting auth differ by approval kind.
    • text
      createApproverRestrictedNativeApprovalAdapter
      still exists as a compatibility wrapper, but new code should prefer the capability builder and expose
      text
      approvalCapability
      on the plugin.

    For hot channel entrypoints, prefer the narrower runtime subpaths when you only need one part of that family:

    • text
      openclaw/plugin-sdk/approval-auth-runtime
    • text
      openclaw/plugin-sdk/approval-client-runtime
    • text
      openclaw/plugin-sdk/approval-delivery-runtime
    • text
      openclaw/plugin-sdk/approval-gateway-runtime
    • text
      openclaw/plugin-sdk/approval-handler-adapter-runtime
    • text
      openclaw/plugin-sdk/approval-handler-runtime
    • text
      openclaw/plugin-sdk/approval-native-runtime
    • text
      openclaw/plugin-sdk/approval-reply-runtime
    • text
      openclaw/plugin-sdk/channel-runtime-context

    Likewise, prefer

    text
    openclaw/plugin-sdk/setup-runtime
    ,
    text
    openclaw/plugin-sdk/setup-adapter-runtime
    ,
    text
    openclaw/plugin-sdk/reply-runtime
    ,
    text
    openclaw/plugin-sdk/reply-dispatch-runtime
    ,
    text
    openclaw/plugin-sdk/reply-reference
    , and
    text
    openclaw/plugin-sdk/reply-chunking
    when you do not need the broader umbrella surface.

    For setup specifically:

    • text
      openclaw/plugin-sdk/setup-runtime
      covers the runtime-safe setup helpers: import-safe setup patch adapters (
      text
      createPatchedAccountSetupAdapter
      ,
      text
      createEnvPatchedAccountSetupAdapter
      ,
      text
      createSetupInputPresenceValidator
      ), lookup-note output,
      text
      promptResolvedAllowFrom
      ,
      text
      splitSetupEntries
      , and the delegated setup-proxy builders
    • text
      openclaw/plugin-sdk/setup-adapter-runtime
      is the narrow env-aware adapter seam for
      text
      createEnvPatchedAccountSetupAdapter
    • text
      openclaw/plugin-sdk/channel-setup
      covers the optional-install setup builders plus a few setup-safe primitives:
      text
      createOptionalChannelSetupSurface
      ,
      text
      createOptionalChannelSetupAdapter
      ,

    If your channel supports env-driven setup or auth and generic startup/config flows should know those env names before runtime loads, declare them in the plugin manifest with

    text
    channelEnvVars
    . Keep channel runtime
    text
    envVars
    or local constants for operator-facing copy only.

    If your channel can appear in

    text
    status
    ,
    text
    channels list
    ,
    text
    channels status
    , or SecretRef scans before the plugin runtime starts, add
    text
    openclaw.setupEntry
    in
    text
    package.json
    . That entrypoint should be safe to import in read-only command paths and should return the channel metadata, setup-safe config adapter, status adapter, and channel secret target metadata needed for those summaries. Do not start clients, listeners, or transport runtimes from the setup entry.

    Keep the main channel entry import path narrow too. Discovery can evaluate the entry and the channel plugin module to register capabilities without activating the channel. Files such as

    text
    channel-plugin-api.ts
    should export the channel plugin object without importing setup wizards, transport clients, socket listeners, subprocess launchers, or service startup modules. Put those runtime pieces in modules loaded from
    text
    registerFull(...)
    , runtime setters, or lazy capability adapters.

    text
    createOptionalChannelSetupWizard
    ,
    text
    DEFAULT_ACCOUNT_ID
    ,
    text
    createTopLevelChannelDmPolicy
    ,
    text
    setSetupChannelEnabled
    , and
    text
    splitSetupEntries

    • use the broader
      text
      openclaw/plugin-sdk/setup
      seam only when you also need the heavier shared setup/config helpers such as
      text
      moveSingleAccountChannelSectionToDefaultAccount(...)

    If your channel only wants to advertise "install this plugin first" in setup surfaces, prefer

    text
    createOptionalChannelSetupSurface(...)
    . The generated adapter/wizard fail closed on config writes and finalization, and they reuse the same install-required message across validation, finalize, and docs-link copy.

    For other hot channel paths, prefer the narrow helpers over broader legacy surfaces:

    • text
      openclaw/plugin-sdk/account-core
      ,
      text
      openclaw/plugin-sdk/account-id
      ,
      text
      openclaw/plugin-sdk/account-resolution
      , and
      text
      openclaw/plugin-sdk/account-helpers
      for multi-account config and default-account fallback
    • text
      openclaw/plugin-sdk/inbound-envelope
      and
      text
      openclaw/plugin-sdk/inbound-reply-dispatch
      for inbound route/envelope and record-and-dispatch wiring
    • text
      openclaw/plugin-sdk/messaging-targets
      for target parsing/matching
    • text
      openclaw/plugin-sdk/outbound-media
      and
      text
      openclaw/plugin-sdk/outbound-runtime
      for media loading plus outbound identity/send delegates and payload planning
    • text
      buildThreadAwareOutboundSessionRoute(...)
      from
      text
      openclaw/plugin-sdk/channel-core
      when an outbound route should preserve an explicit
      text
      replyToId
      /
      text
      threadId
      or recover the current
      text
      :thread:
      session after the base session key still matches. Provider plugins can override precedence, suffix behavior, and thread id normalization when their platform has native thread delivery semantics.
    • text
      openclaw/plugin-sdk/thread-bindings-runtime
      for thread-binding lifecycle and adapter registration
    • text
      openclaw/plugin-sdk/agent-media-payload
      only when a legacy agent/media payload field layout is still required
    • text
      openclaw/plugin-sdk/telegram-command-config
      for Telegram custom-command normalization, duplicate/conflict validation, and a fallback-stable command config contract

    Auth-only channels can usually stop at the default path: core handles approvals and the plugin just exposes outbound/auth capabilities. Native approval channels such as Matrix, Slack, Telegram, and custom chat transports should use the shared native helpers instead of rolling their own approval lifecycle.

    Inbound mention policy

    Keep inbound mention handling split in two layers:

    • plugin-owned evidence gathering
    • shared policy evaluation

    Use

    text
    openclaw/plugin-sdk/channel-mention-gating
    for mention-policy decisions. Use
    text
    openclaw/plugin-sdk/channel-inbound
    only when you need the broader inbound helper barrel.

    Good fit for plugin-local logic:

    • reply-to-bot detection
    • quoted-bot detection
    • thread-participation checks
    • service/system-message exclusions
    • platform-native caches needed to prove bot participation

    Good fit for the shared helper:

    • text
      requireMention
    • explicit mention result
    • implicit mention allowlist
    • command bypass
    • final skip decision

    Preferred flow:

    1. Compute local mention facts.
    2. Pass those facts into
      text
      resolveInboundMentionDecision({ facts, policy })
      .
    3. Use
      text
      decision.effectiveWasMentioned
      ,
      text
      decision.shouldBypassMention
      , and
      text
      decision.shouldSkip
      in your inbound gate.
    typescript
    import { implicitMentionKindWhen, matchesMentionWithExplicit, resolveInboundMentionDecision, } from "openclaw/plugin-sdk/channel-inbound"; const mentionMatch = matchesMentionWithExplicit(text, { mentionRegexes, mentionPatterns, }); const facts = { canDetectMention: true, wasMentioned: mentionMatch.matched, hasAnyMention: mentionMatch.hasExplicitMention, implicitMentionKinds: [ ...implicitMentionKindWhen("reply_to_bot", isReplyToBot), ...implicitMentionKindWhen("quoted_bot", isQuoteOfBot), ], }; const decision = resolveInboundMentionDecision({ facts, policy: { isGroup, requireMention, allowedImplicitMentionKinds: requireExplicitMention ? [] : ["reply_to_bot", "quoted_bot"], allowTextCommands, hasControlCommand, commandAuthorized, }, }); if (decision.shouldSkip) return;

    text
    api.runtime.channel.mentions
    exposes the same shared mention helpers for bundled channel plugins that already depend on runtime injection:

    • text
      buildMentionRegexes
    • text
      matchesMentionPatterns
    • text
      matchesMentionWithExplicit
    • text
      implicitMentionKindWhen
    • text
      resolveInboundMentionDecision

    If you only need

    text
    implicitMentionKindWhen
    and
    text
    resolveInboundMentionDecision
    , import from
    text
    openclaw/plugin-sdk/channel-mention-gating
    to avoid loading unrelated inbound runtime helpers.

    The older

    text
    resolveMentionGating*
    helpers remain on
    text
    openclaw/plugin-sdk/channel-inbound
    as compatibility exports only. New code should use
    text
    resolveInboundMentionDecision({ facts, policy })
    .

    Walkthrough

    Package and manifest

    Create the standard plugin files. The `channel` field in `package.json` is what makes this a channel plugin. For the full package-metadata surface, see [Plugin Setup and Config](/plugins/sdk-setup#openclaw-channel):
    text
    <CodeGroup> ```json package.json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "name": "@myorg/openclaw-acme-chat", "version": "1.0.0", "type": "module", "openclaw": { "extensions": ["./index.ts"], "setupEntry": "./setup-entry.ts", "channel": { "id": "acme-chat", "label": "Acme Chat", "blurb": "Connect OpenClaw to Acme Chat." } } } ``` ```json openclaw.plugin.json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "id": "acme-chat", "kind": "channel", "channels": ["acme-chat"], "name": "Acme Chat", "description": "Acme Chat channel plugin", "configSchema": { "type": "object", "additionalProperties": false, "properties": {} }, "channelConfigs": { "acme-chat": { "schema": { "type": "object", "additionalProperties": false, "properties": { "token": { "type": "string" }, "allowFrom": { "type": "array", "items": { "type": "string" } } } }, "uiHints": { "token": { "label": "Bot token", "sensitive": true } } } } } ``` </CodeGroup> `configSchema` validates `plugins.entries.acme-chat.config`. Use it for plugin-owned settings that are not the channel account config. `channelConfigs` validates `channels.acme-chat` and is the cold-path source used by config schema, setup, and UI surfaces before the plugin runtime loads.

    Build the channel plugin object

    The `ChannelPlugin` interface has many optional adapter surfaces. Start with the minimum — `id` and `setup` — and add adapters as you need them.
    text
    Create `src/channel.ts`: ```typescript src/channel.ts theme={"theme":{"light":"min-light","dark":"min-dark"}} import { createChatChannelPlugin, createChannelPluginBase, } from "openclaw/plugin-sdk/channel-core"; import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core"; import { acmeChatApi } from "./client.js"; // your platform API client type ResolvedAccount = { accountId: string | null; token: string; allowFrom: string[]; dmPolicy: string | undefined; }; function resolveAccount( cfg: OpenClawConfig, accountId?: string | null, ): ResolvedAccount { const section = (cfg.channels as Record<string, any>)?.["acme-chat"]; const token = section?.token; if (!token) throw new Error("acme-chat: token is required"); return { accountId: accountId ?? null, token, allowFrom: section?.allowFrom ?? [], dmPolicy: section?.dmSecurity, }; } export const acmeChatPlugin = createChatChannelPlugin<ResolvedAccount>({ base: createChannelPluginBase({ id: "acme-chat", setup: { resolveAccount, inspectAccount(cfg, accountId) { const section = (cfg.channels as Record<string, any>)?.["acme-chat"]; return { enabled: Boolean(section?.token), configured: Boolean(section?.token), tokenStatus: section?.token ? "available" : "missing", }; }, }, }), // DM security: who can message the bot security: { dm: { channelKey: "acme-chat", resolvePolicy: (account) => account.dmPolicy, resolveAllowFrom: (account) => account.allowFrom, defaultPolicy: "allowlist", }, }, // Pairing: approval flow for new DM contacts pairing: { text: { idLabel: "Acme Chat username", message: "Send this code to verify your identity:", notify: async ({ target, code }) => { await acmeChatApi.sendDm(target, `Pairing code: ${code}`); }, }, }, // Threading: how replies are delivered threading: { topLevelReplyToMode: "reply" }, // Outbound: send messages to the platform outbound: { attachedResults: { sendText: async (params) => { const result = await acmeChatApi.sendMessage( params.to, params.text, ); return { messageId: result.id }; }, }, base: { sendMedia: async (params) => { await acmeChatApi.sendFile(params.to, params.filePath); }, }, }, }); ``` For channels that accept both canonical top-level DM keys and legacy nested keys, use the helpers from `plugin-sdk/channel-config-helpers`: `resolveChannelDmAccess`, `resolveChannelDmPolicy`, `resolveChannelDmAllowFrom`, and `normalizeChannelDmPolicy` keep account-local values ahead of inherited root values. Pair the same resolver with doctor repair through `normalizeLegacyDmAliases` so runtime and migration read the same contract. <Accordion title="What createChatChannelPlugin does for you"> Instead of implementing low-level adapter interfaces manually, you pass declarative options and the builder composes them: | Option | What it wires | | -------------------------- | --------------------------------------------------------- | | `security.dm` | Scoped DM security resolver from config fields | | `pairing.text` | Text-based DM pairing flow with code exchange | | `threading` | Reply-to-mode resolver (fixed, account-scoped, or custom) | | `outbound.attachedResults` | Send functions that return result metadata (message IDs) | You can also pass raw adapter objects instead of the declarative options if you need full control. Raw outbound adapters may define a `chunker(text, limit, ctx)` function. The optional `ctx.formatting` carries delivery-time formatting decisions such as `maxLinesPerMessage`; apply it before sending so reply threading and chunk boundaries are resolved once by shared outbound delivery. Send contexts also include `replyToIdSource` (`implicit` or `explicit`) when a native reply target was resolved, so payload helpers can preserve explicit reply tags without consuming an implicit single-use reply slot. </Accordion>

    Wire the entry point

    Create `index.ts`:
    text
    ```typescript index.ts theme={"theme":{"light":"min-light","dark":"min-dark"}} import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; import { acmeChatPlugin } from "./src/channel.js"; export default defineChannelPluginEntry({ id: "acme-chat", name: "Acme Chat", description: "Acme Chat channel plugin", plugin: acmeChatPlugin, registerCliMetadata(api) { api.registerCli( ({ program }) => { program .command("acme-chat") .description("Acme Chat management"); }, { descriptors: [ { name: "acme-chat", description: "Acme Chat management", hasSubcommands: false, }, ], }, ); }, registerFull(api) { api.registerGatewayMethod(/* ... */); }, }); ``` Put channel-owned CLI descriptors in `registerCliMetadata(...)` so OpenClaw can show them in root help without activating the full channel runtime, while normal full loads still pick up the same descriptors for real command registration. Keep `registerFull(...)` for runtime-only work. If `registerFull(...)` registers gateway RPC methods, use a plugin-specific prefix. Core admin namespaces (`config.*`, `exec.approvals.*`, `wizard.*`, `update.*`) stay reserved and always resolve to `operator.admin`. `defineChannelPluginEntry` handles the registration-mode split automatically. See [Entry Points](/plugins/sdk-entrypoints#definechannelpluginentry) for all options.

    Add a setup entry

    Create `setup-entry.ts` for lightweight loading during onboarding:
    text
    ```typescript setup-entry.ts theme={"theme":{"light":"min-light","dark":"min-dark"}} import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; import { acmeChatPlugin } from "./src/channel.js"; export default defineSetupPluginEntry(acmeChatPlugin); ``` OpenClaw loads this instead of the full entry when the channel is disabled or unconfigured. It avoids pulling in heavy runtime code during setup flows. See [Setup and Config](/plugins/sdk-setup#setup-entry) for details. Bundled workspace channels that split setup-safe exports into sidecar modules can use `defineBundledChannelSetupEntry(...)` from `openclaw/plugin-sdk/channel-entry-contract` when they also need an explicit setup-time runtime setter.

    Handle inbound messages

    Your plugin needs to receive messages from the platform and forward them to OpenClaw. The typical pattern is a webhook that verifies the request and dispatches it through your channel's inbound handler:
    text
    ```typescript} registerFull(api) { api.registerHttpRoute({ path: "/acme-chat/webhook", auth: "plugin", // plugin-managed auth (verify signatures yourself) handler: async (req, res) => { const event = parseWebhookPayload(req); // Your inbound handler dispatches the message to OpenClaw. // The exact wiring depends on your platform SDK — // see a real example in the bundled Microsoft Teams or Google Chat plugin package. await handleAcmeChatInbound(api, event); res.statusCode = 200; res.end("ok"); return true; }, }); } ``` <Note> Inbound message handling is channel-specific. Each channel plugin owns its own inbound pipeline. Look at bundled channel plugins (for example the Microsoft Teams or Google Chat plugin package) for real patterns. </Note>

    Test

    Write colocated tests in `src/channel.test.ts`:
    text
    ```typescript src/channel.test.ts theme={"theme":{"light":"min-light","dark":"min-dark"}} import { describe, it, expect } from "vitest"; import { acmeChatPlugin } from "./channel.js"; describe("acme-chat plugin", () => { it("resolves account from config", () => { const cfg = { channels: { "acme-chat": { token: "test-token", allowFrom: ["user1"] }, }, } as any; const account = acmeChatPlugin.setup!.resolveAccount(cfg, undefined); expect(account.token).toBe("test-token"); }); it("inspects account without materializing secrets", () => { const cfg = { channels: { "acme-chat": { token: "test-token" } }, } as any; const result = acmeChatPlugin.setup!.inspectAccount!(cfg, undefined); expect(result.configured).toBe(true); expect(result.tokenStatus).toBe("available"); }); it("reports missing config", () => { const cfg = { channels: {} } as any; const result = acmeChatPlugin.setup!.inspectAccount!(cfg, undefined); expect(result.configured).toBe(false); }); }); ``` ```bash} pnpm test -- <bundled-plugin-root>/acme-chat/ ``` For shared test helpers, see [Testing](/plugins/sdk-testing).

    File structure

    text
    <bundled-plugin-root>/acme-chat/ ├── package.json # openclaw.channel metadata ├── openclaw.plugin.json # Manifest with config schema ├── index.ts # defineChannelPluginEntry ├── setup-entry.ts # defineSetupPluginEntry ├── api.ts # Public exports (optional) ├── runtime-api.ts # Internal runtime exports (optional) └── src/ ├── channel.ts # ChannelPlugin via createChatChannelPlugin ├── channel.test.ts # Tests ├── client.ts # Platform API client └── runtime.ts # Runtime store (if needed)

    Advanced topics

    Threading options

    Fixed, account-scoped, or custom reply modes

    Message tool integration

    describeMessageTool and action discovery

    Target resolution

    inferTargetChatType, looksLikeId, resolveTarget

    Runtime helpers

    TTS, STT, media, subagent via api.runtime

    Channel turn kernel

    Shared inbound turn lifecycle: ingest, resolve, record, dispatch, finalize

    note

    Some bundled helper seams still exist for bundled-plugin maintenance and compatibility. They are not the recommended pattern for new channel plugins; prefer the generic channel/setup/reply/runtime subpaths from the common SDK surface unless you are maintaining that bundled plugin family directly.

    Next steps

    • Provider Plugins — if your plugin also provides models
    • SDK Overview — full subpath import reference
    • SDK Testing — test utilities and contract tests
    • Plugin Manifest — full manifest schema

    Related

    • Plugin SDK setup
    • Building plugins
    • Agent harness plugins

    © 2024 TaskFlow Mirror

    Powered by TaskFlow Sync Engine