Slack

Connect Slack directly with the Slack adapter.


Connect Slack yourself with the Slack adapter. It runs over Socket Mode by default, so no public URL is needed for local development.

Install#

npm install @copilotkit/channels @copilotkit/runtime @tanstack/ai @tanstack/ai-openai

Credentials#

From your Slack app:

SLACK_BOT_TOKEN=xoxb-...   # Bot User OAuth token
SLACK_APP_TOKEN=xapp-...   # App-level token, for Socket Mode

# The runtime runs every channel, so a CopilotKit Intelligence key is required
# even for a direct adapter (free tier available).
COPILOTKIT_INTELLIGENCE_URL=https://your-intelligence-url
COPILOTKIT_API_KEY=cpk-...

Connect#

import { createChannel } from "@copilotkit/channels";
import { slack, defaultSlackContext } from "@copilotkit/channels/slack";
import { CopilotRuntime, CopilotKitIntelligence } from "@copilotkit/runtime/v2";
import { createCopilotNodeListener } from "@copilotkit/runtime/v2/node";
import { agent } from "./agent"; // your TanStack AI agent, see the Quickstart

const channel = createChannel({
  name: "support-bot",
  agent,
  // Slack output-formatting guidance so replies render well in Slack.
  context: [...defaultSlackContext],
  adapters: [
    slack({
      botToken: process.env.SLACK_BOT_TOKEN!,
      appToken: process.env.SLACK_APP_TOKEN!,
      respondTo: {
        directMessages: true,
        appMentions: { reply: "thread" },
        threadReplies: "mentionsOnly",
      },
    }),
  ],
});

channel.onMessage(async ({ thread, message }) => {
  await thread.runAgent({ prompt: message.text });
});

// The runtime drives the channel — it starts the Slack adapter, you don't call
// channel.start() yourself. A CopilotKit Intelligence key runs every channel,
// direct adapters included (free tier available); `ready()` activates it.
const apiUrl = process.env.COPILOTKIT_INTELLIGENCE_URL!;
const runtime = new CopilotRuntime({
  agents: {},
  intelligence: new CopilotKitIntelligence({
    apiUrl,
    wsUrl: apiUrl.replace(/^http/, "ws"),
    apiKey: process.env.COPILOTKIT_API_KEY!,
  }),
  identifyUser: () => ({ id: "demo-user", name: "Demo User" }),
  channels: [channel],
});

const listener = createCopilotNodeListener({ runtime });
await listener.channels?.ready();

respondTo controls when the bot answers: DMs, mentions, and whether it keeps listening in a thread it is part of.

Streaming#

The Slack adapter streams replies as they are produced. The streaming option picks the transport:

  • "native" (default) uses Slack's chat.startStream in threads, and falls back automatically in flat DMs or workspaces where the streaming API is not available.
  • "legacy" edits a single message with repeated chat.update calls.
slack({
  botToken: process.env.SLACK_BOT_TOKEN!,
  appToken: process.env.SLACK_APP_TOKEN!,
  streaming: "native",
});

Declare slash commands in the manifest

A /command only reaches your bot if it is declared in your Slack app manifest with the same name you pass to defineChannelCommand. See commands and reactions.

Built-in tools and context#

The Slack adapter ships helpers you can spread into your channel: a set of tools (including a Slack user-lookup tool) and context that guides Slack formatting and tagging.

import { slack, defaultSlackTools, defaultSlackContext } from "@copilotkit/channels/slack";

createChannel({
  name: "support-bot",
  agent,
  tools: [...defaultSlackTools],
  context: [...defaultSlackContext],
  adapters: [slack({ /* ...credentials */ })],
});