Platform Adapters

Platform adapters handle webhook verification, message parsing, and API calls for each messaging platform. Install only the adapters you need. Browse every available adapter, including community-built ones, on the Adapters page.

Need a browser chat UI? See the Web adapter. It speaks the AI SDK UI stream protocol and works with React (@ai-sdk/react), Vue (@ai-sdk/vue), and Svelte (@ai-sdk/svelte), so one bot can serve Slack, Teams, and a browser chat UI.

Feature matrix

This matrix covers Vercel-maintained official adapters only. For vendor-official and community adapters, see each adapter's page.

Messaging

FeatureDiscordGoogle ChatGitHubGmailInstagramLinearMessengerNotionSlackMicrosoft TeamsTelegramTwilioTwitchWebWhatsApp Business CloudXXChat
Post message
Message repliesOutbound only
Edit messagePartialPosts onlyOwn text messages, age-gated
Delete messagePartialChat, as a moderatorOwn messages
File uploadsUploads and HTTPS URLsUp to 3 native attachmentsPublic media URLsImages, audio, docs
StreamingPost+EditPost+EditBufferedBuffered emailBufferedAgent sessions / Post+EditBufferedPost+EditNativeNative (DMs) / BufferedPost+Edit / Opt-in rich draftsBufferedBufferedNative (SSE)BufferedBufferedAge-gated edits
Scheduled messagesNative

Rich content

FeatureDiscordGoogle ChatGitHubGmailInstagramLinearMessengerNotionSlackMicrosoft TeamsTelegramTwilioTwitchWebWhatsApp Business CloudXXChat
Card formatEmbeds / ComponentsGoogle Chat CardsGFM MarkdownPlain textGeneric / Button TemplatesMarkdownGeneric / Button TemplatesMarkdown fallbackBlock KitAdaptive CardsMarkdownV2 + inline keyboardRCS rich cards + SMS text fallbackPlain textMarkdown only (v1)Interactive messagesPlain textPlain text + URL preview
ButtonsQuick replies / postbacksMax 3, postbackInline keyboardRCS quick-repliesInteractive replies
Link buttonsText linksweb_urlweb_urlInline keyboard URLsRCS call-to-actionRendered as textSingle CTA URLRendered as textTappable URLs
Select menusComponents
TablesGFMASCIIGFMPlain textGFMASCIIFlattened markdownBlock KitAdaptive Card TableNative messages / ASCII cardsASCIIOne line of textGFMASCIIASCII
ChartsBlock Kit
FieldsPlain textASCIIPlain textFormatted textPlain textPlain text
Images in cardsRCS only
Modals

Conversations

FeatureDiscordGoogle ChatGitHubGmailInstagramLinearMessengerNotionSlackMicrosoft TeamsTelegramTwilioTwitchWebWhatsApp Business CloudXXChat
Slash commands
MentionsLabel-based intake
Add reactionsWorkspace EventsLikes only
Remove reactionsWorkspace EventsPartialPartialLikes only
Typing indicatorAgent sessions
Mark as read
Message edit events
Message delete events
DMsRequires delegationEmail to recipientWhispers
Ephemeral messagesNativePermanent private emailNativeTargeted messages
User lookupCachedCachedSeen users
Parent subject
Native client
Custom API endpointMedia REST calls

Message history

FeatureDiscordGoogle ChatGitHubGmailInstagramLinearMessengerNotionSlackMicrosoft TeamsTelegramTwilioTwitchWebWhatsApp Business CloudXXChat
Fetch messagesRequires delegationCached sent onlyCached sent onlyRequires Graph permissionsCachedMessages APIPersisted historyState cacheCached sent onlyDMs via API, posts cached
Fetch single messageCachedCachedCachedPosts via API, DMs cached
Fetch thread infoSynthesized
Fetch channel messagesCachedCachedRequires Graph permissionsCachedState cache
List threadsRequires Graph permissions
Fetch channel infoRequires Graph permissions
Post channel message

Partial support means the feature works with limitations. See individual adapter pages for details.

How adapters work

Each adapter implements the Adapter interface, which the Chat class uses to route events and send messages. When a webhook arrives, the adapter:

  1. Verifies the request signature.
  2. Parses the platform-specific payload into a normalized Message.
  3. Passes the message to the Chat class, which routes it to your handlers.

When your handler posts a reply, the adapter converts it from markdown, an AST, or a card into the platform's native format.

Using multiple adapters

Register multiple adapters and your event handlers work across all of them:

lib/bot.ts
import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";
import { createTeamsAdapter } from "@chat-adapter/teams";
import { createGoogleChatAdapter } from "@chat-adapter/gchat";
import { createRedisState } from "@chat-adapter/state-redis";

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

// This handler fires for mentions on any platform
bot.onNewMention(async (thread) => {
  await thread.subscribe();
  await thread.post("Hello!");
});

Each adapter reads its credentials from environment variables, so you only pass config to override them.

This example uses Redis for state. See State Adapters for all available options.

Each adapter gets a webhook handler at bot.webhooks.<name>, keyed by the name you registered it under.

Customizing an adapter via subclassing

Official adapters expose their extension points as protected members, so you can subclass an adapter to override or extend its behavior without forking the package. Use this to handle a payload type the built-in adapter doesn't cover, intercept verification, or wrap an existing handler.

lib/custom-telegram.ts
import { TelegramAdapter, type TelegramUpdate } from "@chat-adapter/telegram";
import type { WebhookOptions } from "chat";

export class CustomTelegramAdapter extends TelegramAdapter {
  protected override processUpdate(
    update: TelegramUpdate,
    options?: WebhookOptions
  ): void {
    // Handle a payload type the base adapter doesn't, e.g. chat_join_request.
    if ("chat_join_request" in update) {
      this.logger.info("Received chat_join_request", { update });
      return;
    }
    super.processUpdate(update, options);
  }
}

Construct your subclass anywhere you'd construct the base adapter, for example adapters: { telegram: new CustomTelegramAdapter({ ... }) }. Members marked private stay inaccessible. If you need a hook that isn't protected, open an issue.

protected members are broader than the public API and are not yet stable. Their signatures may change in minor releases. Pin the adapter version you build against, watch that adapter's changelog, and override the smallest hook that solves your problem so upgrades stay manageable. If you rely on a particular hook, open an issue so we can consider making it a stable, documented extension point.

Read more