---
title: Slack bot with Next.js and Redis
description: This guide walks through building a Slack bot with Next.js, covering project setup, Slack app configuration, event handling, interactive features, and deployment.
type: guide
prerequisites: []
related:
  - /adapters/slack
  - /docs/cards
  - /docs/modals
  - /docs/streaming
---

## Prerequisites

- Node.js 18+
- [pnpm](https://pnpm.io) (or npm/yarn)
- A Slack workspace where you can install apps
- A Redis instance for state management

## Create a Next.js app

Scaffold a new Next.js project and install Chat SDK dependencies:

```sh title="Terminal"
npx create-next-app@latest my-slack-bot --typescript --app
cd my-slack-bot
pnpm add chat @chat-adapter/slack @chat-adapter/state-redis
```

## Create a Slack app

1. Go to [api.slack.com/apps](https://api.slack.com/apps)
2. Click **Create New App** then **From an app manifest**
3. Select your workspace and paste the following manifest:

```yaml title="slack-manifest.yml"
display_information:
  name: My Bot
  description: A bot built with Chat SDK

features:
  bot_user:
    display_name: My Bot
    always_online: true

oauth_config:
  scopes:
    bot:
      - app_mentions:read
      - channels:history
      - channels:read
      - chat:write
      - groups:history
      - groups:read
      - im:history
      - im:read
      - mpim:history
      - mpim:read
      - reactions:read
      - reactions:write
      - users:read

settings:
  event_subscriptions:
    request_url: https://your-domain.com/api/webhooks/slack
    bot_events:
      - app_mention
      - message.channels
      - message.groups
      - message.im
      - message.mpim
  interactivity:
    is_enabled: true
    request_url: https://your-domain.com/api/webhooks/slack
  org_deploy_enabled: false
  socket_mode_enabled: false
  token_rotation_enabled: false
```

4. Replace `https://your-domain.com/api/webhooks/slack` with your deployed webhook URL
5. Click **Create**

### Get credentials

After creating the app:

1. Go to **OAuth & Permissions**, click **Install to Workspace**, and copy the **Bot User OAuth Token** (`xoxb-...`) — you'll need this as `SLACK_BOT_TOKEN`
2. Go to **Basic Information** → **App Credentials** and copy the **Signing Secret** — you'll need this as `SLACK_SIGNING_SECRET`

If you're distributing the app across multiple workspaces via OAuth instead of installing it to one workspace, configure `clientId` and `clientSecret` on the Slack adapter and pass the same redirect URI used during the authorize step into `handleOAuthCallback(request, { redirectUri })` in your callback route.

## Configure environment variables

Create a `.env.local` file in your project root:

```bash title=".env.local"
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_SIGNING_SECRET=your-signing-secret
REDIS_URL=redis://localhost:6379
```

## Create the bot

Create `lib/bot.ts` with a `Chat` instance configured with the Slack adapter:

```typescript title="lib/bot.ts" lineNumbers
import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";
import { createRedisState } from "@chat-adapter/state-redis";

export const bot = new Chat({
  userName: "mybot",
  adapters: {
    slack: createSlackAdapter(),
  },
  state: createRedisState(),
});

// Respond when someone @mentions the bot
bot.onNewMention(async (thread) => {
  await thread.subscribe();
  await thread.post("Hello! I'm listening to this thread now.");
});

// Respond to follow-up messages in subscribed threads
bot.onSubscribedMessage(async (thread, message) => {
  await thread.post(`You said: ${message.text}`);
});
```

The adapter auto-detects `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` from your environment, and `createRedisState()` reads `REDIS_URL` automatically.

`onNewMention` fires when a user @mentions your bot. Calling `thread.subscribe()` tells the SDK to track that thread, so subsequent messages trigger `onSubscribedMessage`.

## Create the webhook route

Create a dynamic API route that handles incoming webhooks:

```typescript title="app/api/webhooks/[platform]/route.ts" lineNumbers
import { after } from "next/server";
import { bot } from "@/lib/bot";

type Platform = keyof typeof bot.webhooks;

export async function POST(
  request: Request,
  context: RouteContext<"/api/webhooks/[platform]">
) {
  const { platform } = await context.params;

  const handler = bot.webhooks[platform as Platform];
  if (!handler) {
    return new Response(`Unknown platform: ${platform}`, { status: 404 });
  }

  return handler(request, {
    waitUntil: (task) => after(() => task),
  });
}
```

This creates a `POST /api/webhooks/slack` endpoint. The `waitUntil` option ensures message processing completes after the HTTP response is sent — required on serverless platforms where the function would otherwise terminate before your handlers finish.

## Test locally

1. Start your development server (`pnpm dev`)
2. Expose it with a tunnel (e.g. `ngrok http 3000`)
3. Update the Slack Event Subscriptions **Request URL** to your tunnel URL
4. Invite your bot to a Slack channel (`/invite @mybot`)
5. @mention the bot — it should respond with "Hello! I'm listening to this thread now."
6. Reply in the thread — it should echo your message back

## Add interactive features

Chat SDK supports rich interactive messages using a JSX-like syntax. Update your bot to send cards with buttons:

```typescript title="lib/bot.ts" lineNumbers
import { Chat, Card, CardText as Text, Actions, Button, Divider } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";
import { createRedisState } from "@chat-adapter/state-redis";

export const bot = new Chat({
  userName: "mybot",
  adapters: {
    slack: createSlackAdapter(),
  },
  state: createRedisState(),
});

bot.onNewMention(async (thread) => {
  await thread.subscribe();
  await thread.post(
    <Card title="Welcome!">
      <Text>I'm now listening to this thread. Try clicking a button:</Text>
      <Divider />
      <Actions>
        <Button id="hello" style="primary">Say Hello</Button>
        <Button id="info">Show Info</Button>
      </Actions>
    </Card>
  );
});

bot.onAction("hello", async (event) => {
  await event.thread.post(`Hello, ${event.user.fullName}!`);
});

bot.onAction("info", async (event) => {
  await event.thread.post(`You're on ${event.thread.adapter.name}.`);
});
```

<Callout type="info">
  The file extension must be `.tsx` (not `.ts`) when using JSX components like `Card` and `Button`. Make sure your `tsconfig.json` has `"jsx": "react-jsx"` and `"jsxImportSource": "chat"`.
</Callout>

## Deploy to Vercel

Deploy your bot to Vercel:

```sh title="Terminal"
vercel deploy
```

After deployment, set your environment variables in the Vercel dashboard (`SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, `REDIS_URL`). If your manifest used a placeholder URL, update the **Event Subscriptions** and **Interactivity** Request URLs in your [Slack app settings](https://api.slack.com/apps) to your production URL.

## Next steps

- [Cards](/docs/cards) — Build rich interactive messages with buttons, fields, and selects
- [Modals](/docs/modals) — Open forms and dialogs from button clicks
- [Streaming](/docs/streaming) — Stream AI-generated responses to chat
- [Actions](/docs/actions) — Handle button clicks, select menus, and other interactions
- [Slack adapter](/adapters/slack) — Multi-workspace OAuth, token encryption, and full configuration reference
- [State Adapters](/docs/state) — PostgreSQL, ioredis, and other state adapter options
