Channels Overview

Run your CopilotKit agent as a bot inside Slack, Teams, WhatsApp, Telegram, and Discord.


Channels let you take a CopilotKit agent and run it as a bot inside a chat app. The user talks to your agent in Slack, Teams, WhatsApp, Telegram, or Discord, and your agent answers right there in the thread.

You write the bot once. The same agent, tools, commands, and interactive cards run on every platform you attach.

A channel, end to end#

Three pieces: a TanStack AI agent, JSX turned on, and a channel that plugs the agent into a platform and replies with clickable UI.

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

The agent#

A Built-in Agent in TanStack factory mode. You own the model call with TanStack AI; the agent turns its stream into the events the channel renders.

agent.ts
import { BuiltInAgent, convertInputToTanStackAI } from "@copilotkit/runtime/v2";
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";

export const agent = new BuiltInAgent({
  type: "tanstack",
  factory: (ctx) => {
    const { messages, systemPrompts, tools } = convertInputToTanStackAI(ctx.input);
    return chat({
      adapter: openaiText("gpt-5.5"),
      messages,
      systemPrompts: ["You are a helpful support assistant.", ...systemPrompts],
      tools,
      abortController: ctx.abortController,
    });
  },
});

Turn on JSX#

Replies render as native UI. Point the JSX runtime at the channels package.

tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@copilotkit/channels"
  }
}

The channel#

Declare a channel, then answer messages. A reply can be the agent's own words, or a card whose button you handle right where you wrote it.

bot.ts
import { createChannel } from "@copilotkit/channels";
import { CopilotRuntime, CopilotKitIntelligence } from "@copilotkit/runtime/v2";
import { createCopilotNodeListener } from "@copilotkit/runtime/v2/node";
import { Message, Section, Actions, Button } from "@copilotkit/channels/ui";
import { agent } from "./agent";

// A managed channel: Intelligence hosts the transport, so no adapter.
const channel = createChannel({
  name: "support-bot",
  agent,
});

channel.onMessage(async ({ thread, message }) => {
  // let the agent answer
  await thread.runAgent({ prompt: message.text });

  // then post a card, and handle the click right here
  await thread.post(
    <Message>
      <Section>{"Did that answer your question?"}</Section>
      <Actions>
        <Button 
          onClick={async ({ thread }) => {
            await thread.post("Glad I could help!");
          }}
        >
          Yes, thanks
        </Button>
      </Actions>
    </Message>,
  );
});

// The runtime owns the channel's lifecycle. A CopilotKit Intelligence key runs
// any channel (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();
// Mount the listener on an HTTP server to keep the process running (see the Quickstart).

That is a working bot: a TanStack AI agent, answering across platforms, replying with clickable UI. The Quickstart adds the dashboard setup and environment, and the UI library shows how to register that card so its button still works after a restart.

Two ways to connect a platform#

Every channel runs through the Intelligence runtime, so both paths need a CopilotKit Intelligence key (free tier available). The difference is who holds the platform bot credentials — either way, the runtime drives the channel.

ManagedDirect (platform adapter)
Who holds the platform tokensCopilotKit IntelligenceYou supply them to the adapter
Add a platformAttach it in the Intelligence dashboardAdd another adapter in code
Best forShipping fast, many platforms, no platform secrets in your appOwning one platform's connection end to end

With the managed path your process holds no Slack or Teams tokens: Intelligence receives the platform event, delivers the turn to your bot, runs the same handlers, and does the credentialed send back. With a direct adapter you supply the platform credentials, but the runtime still owns the channel's lifecycle. Start with managed. See Platforms for what Intelligence handles for you and the per-platform setup.

What you can build#