---
title: "App Manifests"
description: "Explore manifest features (shortcuts, slash commands, assistant view), bot scopes, and event subscriptions. Add `reactions:read` and subscribe to reaction events, reinstall, and update your scope truth table."
canonical_url: "https://vercel.com/academy/slack-agents/manifest-and-scopes"
md_url: "https://vercel.com/academy/slack-agents/manifest-and-scopes.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-03-10T12:25:18.284Z"
content_type: "lesson"
course: "slack-agents"
course_title: "Slack Agents on Vercel with the AI SDK"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# App Manifests

# App Manifests: Version Control for Your Slack App Configuration

Clicking through 20+ settings in the Slack app dashboard for every environment is tedious and error prone. "Works on my machine" because someone forgot to enable a scope in staging. Permission creep as apps accumulate unnecessary scopes with no audit trail. Manifests fix this: one JSON file defines your entire app config, versioned in git, deployed consistently across dev/staging/prod.

## Outcome

Understand how scopes enable features and how to modify manifests without breaking your app.

## Fast Track

1. Add `reactions:read` scope to `manifest.json`
2. Subscribe to `reaction_added` and `reaction_removed` events
3. Create handlers and verify events fire in logs

## Understanding Scopes

Scopes are permissions your bot requests. Too few = broken features. Too many = security risk and user distrust. Start minimal, add as needed. Every scope should map to a specific feature.

## Hands-On Exercise 2.1

Add reaction tracking to your bot:

**Requirements:**

1. Add `reactions:read` scope to `manifest.json` under `oauth_config.scopes.bot`
2. Subscribe to `reaction_added` and `reaction_removed` events in `settings.event_subscriptions.bot_events`
3. Create `server/listeners/events/reactions.ts` with handlers that log reaction details
4. Register handlers in `server/listeners/events/index.ts`

**Implementation hints:**

- Run `slack run` to apply manifest changes (approve when prompted)
- Test by reacting to messages in channels where the bot is present
- Check logs for reaction events with `reaction`, `item`, `user` fields

## Try It

1. **Apply manifest changes:**
   ```bash
   slack run
   # Approve when prompted: "Do you want to apply manifest changes?"
   ```

2. **Test reaction events:**
   - Find a channel where your bot is present
   - React to any message with 👍
   - Check terminal logs for:
     ```
     [INFO] reaction_added { reaction: "thumbsup", item: {...}, user: "U..." }
     ```
   - Remove the reaction, verify `reaction_removed` logs

3. **Verify scope in dashboard:**
   - Open [api.slack.com/apps](https://api.slack.com/apps) → your app → Event Subscriptions
   - Confirm "Verified" status and `reaction_added`/`reaction_removed` appear in subscribed events

## Troubleshooting

**Events not firing:**

- Ensure bot is actually in the channel (invite it with `/invite @bot`)
- Reinstall app after manifest changes (`slack run` handles this)
- Check that manifest validation passed (no errors during `slack run`)

**Scope mismatch errors:**

- Every event requires matching scopes (`reaction_added` needs `reactions:read`)
- Slack validates this and will reject inconsistent manifests
- Fix by adding the missing scope and re-running `slack run`

## Commit

```bash
git add -A
git commit -m "feat(manifest): add reactions:read and reaction event handlers

- Add reactions:read scope to manifest
- Subscribe to reaction_added and reaction_removed events
- Create reaction event handlers with structured logging
- Verify events fire when reactions are added/removed"
```

## Done-When

- [x] `reactions:read` scope added to `manifest.json`
- [x] Subscribed to `reaction_added` and `reaction_removed` events
- [x] Created `server/listeners/events/reactions.ts` with handlers
- [x] Handlers registered in `server/listeners/events/index.ts`
- [x] Events fire in logs when reacting to messages

## Step by Step Solution

Add an events listener file and register it:

```typescript title="/slack-agent/server/listeners/events/reactions.ts"
import type { AllMiddlewareArgs, SlackEventMiddlewareArgs } from "@slack/bolt";

/**
 * Handle reaction events for basic monitoring and future state/metrics.
 * Requires `reactions:read` scope and subscription to `reaction_added` and `reaction_removed`.
 */
export const reactionAddedCallback = async ({
  event,
  logger,
}: AllMiddlewareArgs & SlackEventMiddlewareArgs<"reaction_added">) => {
  try {
    logger.info(
      {
        reaction: event.reaction,
        item: event.item,
        user: event.user,
        item_user: event.item_user,
        event_ts: event.event_ts,
      },
      "reaction_added",
    );
  } catch (error) {
    logger.error("reaction_added handler failed:", error);
  }
};

export const reactionRemovedCallback = async ({
  event,
  logger,
}: AllMiddlewareArgs & SlackEventMiddlewareArgs<"reaction_removed">) => {
  try {
    logger.info(
      {
        reaction: event.reaction,
        item: event.item,
        user: event.user,
        item_user: event.item_user,
        event_ts: event.event_ts,
      },
      "reaction_removed",
    );
  } catch (error) {
    logger.error("reaction_removed handler failed:", error);
  }
};

export default { reactionAddedCallback, reactionRemovedCallback };
```

Register in your events index:

```typescript title="/slack-agent/server/listeners/events/index.ts" {5,11-12}
import type { App } from "@slack/bolt";
import appHomeOpenedCallback from "./app-home-opened";
import appMentionCallback from "./app-mention";
import { assistantThreadStartedCallback } from "./assistant-thread-started";
import { reactionAddedCallback, reactionRemovedCallback } from "./reactions";

const register = (app: App) => {
  app.event("app_home_opened", appHomeOpenedCallback);
  app.event("app_mention", appMentionCallback);
  app.event("assistant_thread_started", assistantThreadStartedCallback);
  app.event("reaction_added", reactionAddedCallback);
  app.event("reaction_removed", reactionRemovedCallback);
};

export default { register };
```

#### Manifest changes (diff)

Add `reactions:read` and subscribe to reaction events:

```diff title="/slack-agent/manifest.json"
@@ oauth_config.scopes.bot @@
   "scopes": {
     "bot": [
       "channels:history",
       "chat:write",
       "commands",
       "app_mentions:read",
       "groups:history",
       "im:history",
       "mpim:history",
       "assistant:write",
       "reactions:write",
 +     "reactions:read"
     ]
   }

@@ settings.event_subscriptions.bot_events @@
   "bot_events": [
     "app_home_opened",
     "app_mention",
     "assistant_thread_context_changed",
     "assistant_thread_started",
 +   "reaction_added",
 +   "reaction_removed",
     "message.channels",
     "message.groups",
     "message.im",
     "message.mpim"
   ]
```


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
