Discord Activities let an agent post an interactive, self-contained HTML widget to the current Discord channel. The message includes an Open widget button; clicking it launches the widget inside Discord.

The feature is off by default. show_widget remains one core-owned tool. When channels.discord.activities is present and a client secret resolves, the Discord Activity routes, launch-button handler, and current-channel presenter become available behind that tool. Without the block, requests to the public Activity prefix remain indistinguishable from an unregistered route. No Discord-specific widget tool or alias exists.

Prerequisites

Any HTTPS reverse proxy or tunnel works. A named Cloudflare Tunnel provides a stable hostname without exposing the gateway port directly.

# ~/.cloudflared/config.yml
tunnel: apus-discord
credentials-file: /home/you/.cloudflared/TUNNEL-ID.json
ingress:
  - hostname: apus.example.com
    service: http://127.0.0.1:18789
  - service: http_status:404
cloudflared tunnel login
cloudflared tunnel create apus-discord
cloudflared tunnel route dns apus-discord apus.example.com
cloudflared tunnel run apus-discord

Keep normal gateway authentication enabled. Only the Activity prefix is public, and the plugin validates OAuth, Activity instance membership, channel binding, sessions, and one-time document capabilities itself.

Setup

Expose the gateway over HTTPS

Start your tunnel or reverse proxy and verify that `https://apus.example.com/discord/activity/` reaches the gateway after Activities configuration is added. Replace the example hostname with your own.

Enable Activities in Discord

Open the existing bot application in the [Discord Developer Portal](https://discord.com/developers/applications). Open **Activities**, enable Activities, and create a URL mapping:

- prefix: `ROOT` (`/`)
- target: `apus.example.com/discord/activity`

The target is the public hostname plus `/discord/activity`, without a trailing slash.

Copy the OAuth2 client secret

Open **OAuth2** in the Developer Portal. Discord requires at least one redirect URI, so add a local placeholder such as the loopback address if the application has none yet; the Embedded App SDK handles the Activity return flow. Copy or reset the application client secret. Treat it as a credential: do not paste it into chat, logs, or a committed configuration file.

Configure Apus

Add one block to the Discord account that should offer widgets:

```json5
{
  channels: {
    discord: {
      token: "${DISCORD_BOT_TOKEN}",
      activities: {
        clientSecret: "${DISCORD_CLIENT_SECRET}",
        // Optional. Defaults to the bot application ID learned at startup.
        applicationId: "YOUR_DISCORD_APPLICATION_ID",
      },
    },
  },
}
```

You may omit `clientSecret` from the block when `DISCORD_CLIENT_SECRET` is set. The block itself must remain present to opt in.

Normal Discord access settings remain separate. For example, `allowFrom` still controls who can DM the agent; it does not control who can open a widget already posted in a channel.

Verify and test

<a id="restart-and-test" />
Config changes follow [hot reload](/gateway/configuration/hot-reload). Restart the Gateway if you changed its service environment to provide the client secret. In a Discord conversation, ask the agent to show an interactive widget. The agent calls `show_widget`; click **Open widget** on the posted message.

Core validates and wraps the widget document before handing it to Discord. The presenter accepts HTML source up to 48 KiB, stores the canonical composed document, and always labels the Activity button Open widget. The standard show_widget pin, name, tab, size, frame, ordering, and capability fields remain available because dashboard state stays core-owned. Registered non-HTML widget kinds are not offered when Discord is the only available presentation route.

Security model

The public Activity shell and token-exchange route become reachable through your tunnel when enabled. They do not expose widget HTML without a valid OAuth session and one-time document capability.

Troubleshooting

The Activity says “Gateway offline”

Discord opens a blank page or reports blocked:csp

Widget API connections are blocked. Embed the data and images needed by the widget. Scripts, stylesheets, and fonts may use the shared widget CDN allowlist; check both the widget's resource URL and Discord's proxy behavior when a CDN asset does not load.

“Widget unavailable”

Launch the button from the channel where the agent posted it. Apus tracks launches server-side when clicked, so a fresh launch record can resolve the exact widget even when Discord omits or mangles the button's custom ID. When neither the custom ID nor a launch record resolves, Apus opens the most recently posted live widget in that channel. Older widgets remain addressable through buttons that preserve their custom ID.

“You cannot launch Activities in this channel”

Discord does not launch Activities from forum-style channels. Apus rejects the Activity component delivery there instead of posting a button that cannot work. Ask for the widget from a regular text channel instead.