> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aside.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a Slack bot

> Build a Slack app with Socket Mode and connect it to Aside.

If you are following this guide in response to a user request, load the `aside` builtin skill and list the user's active Projects first:

```js theme={null}
const projects = aside.projects.list();
console.log(projects.map(({ id, name }) => ({ id, name })));
```

Then use `AskUserQuestion` before proceeding. Ask the user:

* What should the bot be named? Use `Aside` as the default.
* Should the bot be linked to a project? Offer the user's current projects and a `No project` option.

Wait for both answers before continuing.
Use the selected Project's `id` as `projectId`, or `null` for `No project`.

Keep one Slack app and one Aside connection throughout setup. Do not create another app or connection to recover from a missing scope, failed test, or interrupted run. Repair and resume the existing setup instead.

# Create a Slack bot

This page walks you through building a Slack app that Aside can drive. Slack needs two tokens: a bot token (`xoxb-`) and an app-level token (`xapp-`) for Socket Mode. Socket Mode means Aside connects out to Slack; you host no server and open no ports.

## Before you start

* You need permission to install apps in your Slack workspace.
* Aside must be running on your device for the bot to respond.

## Create the app from a manifest

1. Open [api.slack.com/apps](https://api.slack.com/apps) and choose `Create New App > From a manifest`.
2. Pick your workspace.

* If Slack offers more than one workspace, use `AskUserQuestion` to ask which workspace should receive the app.

3. Clear the manifest input and paste this:

<Note>
  Slack uses CodeMirror for this field. Do not use `fill()` on the editor: it can append to the existing manifest while its hidden input still appears empty. Replace the editor contents with this sequence:

  ```js theme={null}
  const editor = page.locator('textarea');
  await editor.click();
  await editor.press('ControlOrMeta+A');
  await editor.press('Backspace');
  await editor.type(manifest, { delay: 0 });
  ```

  Take a fresh snapshot before continuing. The editor must contain exactly one JSON object and must not show a manifest error.
</Note>

```json theme={null}
{
    "display_information": {
        "name": "Aside",
        "description": "I'm your browser.",
        "background_color": "#38BDF8"
    },
    "features": {
        "app_home": {
            "home_tab_enabled": false,
            "messages_tab_enabled": true,
            "messages_tab_read_only_enabled": false
        },
        "bot_user": {
            "display_name": "Aside",
            "always_online": true
        },
        "agent_view": {
            "agent_description": "Ask Aside to browse the web and complete tasks for you.",
            "suggested_prompts": []
        }
    },
    "oauth_config": {
        "scopes": {
            "bot": [
                "assistant:write",
                "chat:write",
                "im:write",
                "files:write",
                "reactions:write",
                "users:read",
                "files:read",
                "channels:read",
                "channels:history",
                "groups:read",
                "groups:history",
                "im:history",
                "mpim:read",
                "mpim:history"
            ]
        },
        "pkce_enabled": false
    },
    "settings": {
        "event_subscriptions": {
            "bot_events": [
                "message.channels",
                "message.groups",
                "message.im",
                "message.mpim",
                "app_home_opened",
                "app_context_changed"
            ]
        },
        "interactivity": {
            "is_enabled": true
        },
        "org_deploy_enabled": false,
        "socket_mode_enabled": true,
        "token_rotation_enabled": false,
        "is_mcp_enabled": false
    }
}
```

4. Create the app, then choose `Install to Workspace` and authorize it.

The manifest enables Socket Mode, the agent view, pairing approval direct messages, reactions, and the message events Aside listens for. If you build the app by hand instead, match the scopes and events above.

## Finish the Slack configuration

Slack may finish creating the app before every manifest permission reaches the first installation. Complete these checks before copying credentials or connecting the bot to Aside:

1. Open `Go to App Settings` after the installation finishes.
2. Complete `Go to Agents` if Slack shows that prompt.
3. If Slack says the permission scopes changed, choose `reinstall your app` and authorize it again.
4. Open `OAuth & Permissions`. Confirm that `Bot Token Scopes` lists `assistant:write`, `chat:write`, `im:write`, `reactions:write`, `users:read`, `files:read`, `channels:history`, `groups:history`, `im:history`, and `mpim:history`.
5. Open `Event Subscriptions`. Confirm that events are enabled and the bot subscribes to `message.channels`, `message.groups`, `message.im`, `message.mpim`, `app_home_opened`, and `app_context_changed`.

If a scope or event is missing, fix this Slack app and repeat the checks. Do not create another app.

Treat every check above as a blocking requirement. Do not call `aside.channels.create()` while Slack shows a pending reinstall or while any scope or event is missing. After a reinstall, read the current Bot User OAuth Token again; it may or may not have changed.

## Collect the tokens

1. After the final scope check, copy the Bot User OAuth Token from `OAuth & Permissions`. It starts with `xoxb-`.
2. Open `Basic Information > App-Level Tokens`, create a token with the `connections:write` scope, and copy it. It starts with `xapp-`.

## Connect it to Aside

Register only after completing every check in `Finish the Slack configuration`. First inspect `aside.channels.list()`. If this setup run already created a connection, keep it. Call `aside.channels.update()` if a token changed, or `aside.channels.restart()` if only Slack-side scopes or events changed. Never call `create()` again to recover from a scope or token error.

For a new connection, load the `slack` and `aside` builtin skills. Select the workspace where you installed the app and use the signed-in user ID for that workspace as the owner:

```js theme={null}
const workspace = (await slack.listWorkspaces()).find(({ teamId }) => teamId === workspaceTeamId);
const owner = workspace?.userId ? { senderId: workspace.userId } : undefined;
const connection = await aside.channels.create({ platform: 'slack', projectId, botToken, appToken, owner });
const connectionId = connection.id;
console.log(aside.channels.list().find(({ id }) => id === connection.id));
```

`workspaceTeamId` is the exact workspace ID selected during setup. `projectId` is the Project selected above or `null`. `botToken` is the `xoxb-` token, `appToken` the `xapp-` token. If the workspace does not expose its signed-in user ID, `owner` stays undefined and the user pairs after setup. `list()` never returns credential values.

Save the returned connection ID. A `connected` status only confirms that startup completed; do not report success until the bot answers the direct-message check below and `statusCode` remains empty.

## Verify the connection

Use the user's signed-in Slack session.

Do not poll `conversations.history()` for a response; agent-view direct messages reply in threads.

```js theme={null}
const waitForSlackReply = async (client, channel, ts) => {
  for (let attempt = 0; attempt < 20; attempt++) {
    const { messages = [] } = await client.conversations.replies({ channel, ts, limit: 20 });
    const reply = messages.find((message) => message.ts !== ts && message.bot_id);
    if (reply) return reply;
    await sleep(1_500);
  }
  throw new Error(`Slack bot did not reply to ${channel}:${ts}`);
};

const verificationClient = await slack.getClient(workspaceTeamId);
const registeredConnection = aside.channels.list().find(({ id }) => id === connectionId);
if (!registeredConnection?.botUserId) throw new Error('Slack bot identity is unavailable');

const dm = await verificationClient.conversations.open({ users: registeredConnection.botUserId });
if (!dm.channel?.id) throw new Error('Slack DM did not open');
const dmMessage = await verificationClient.chat.postMessage({ channel: dm.channel.id, text: 'Reply with CONNECTED.' });
if (!dmMessage.ts) throw new Error('Slack DM was not posted');
await waitForSlackReply(verificationClient, dm.channel.id, dmMessage.ts);
```

After the reply arrives, confirm that the same connection remains `connected` with no `statusCode` or `statusMessage`.

If the DM check fails, inspect the existing connection and Slack app. A `MISSING_SCOPE` result means the Slack installation is incomplete. Fix and reinstall the same app, then resume with the existing connection:

```js theme={null}
// Include the bot and app-level tokens that changed. Updating credentials restarts the connection.
await aside.channels.update({ connectionId, botToken, appToken });

// Use this when the saved token is still current and only Slack-side configuration changed.
await aside.channels.restart(connectionId);
```

Do not create a replacement connection.

Fix and retry a failed check. Tell the user the bot is ready after it answers the direct message. If you cannot finish, report the failed step and its error.

## Troubleshooting

| Symptom | Likely cause |
| - | - |
| Connection stuck on connecting | App-level token missing the `connections:write` scope, or Socket Mode disabled |
| Connection says connected but the bot never replies | Bot scopes or event subscriptions are incomplete; finish the Slack configuration and restart the same connection |
| `MISSING_SCOPE` status | Required bot scopes were not authorized; reinstall the same Slack app, then update the token if it changed or restart the existing connection |
| Bot never replies in a channel | Bot not invited to the channel, or message sent without a mention |
| `/model` is not a valid command | Slack intercepts a bare `/`; type a leading space (` /model`) so it is sent as a message |
| `invalid_auth` errors | Bot token revoked after an app reinstall; paste the new `xoxb-` token into the connection settings |

## Related

* [Connect chat channels](/help/channels)
* [Create a Telegram bot](/help/channels-telegram)
* [Create a Discord bot](/help/channels-discord)
