Chat
The main entry point for creating a multi-platform chat bot.
The Chat class coordinates adapters, state, and event handlers. Create one instance and register handlers for different event types.
import { Chat } from "chat";Constructor
const bot = new Chat(config);Prop
Type
Event handlers
onNewMention
Fires when the bot is @-mentioned in a thread it has not subscribed to. This is the primary entry point for new conversations.
bot.onNewMention(async (thread, message) => {
await thread.subscribe();
await thread.post("Hello!");
});Prop
Type
onSubscribedMessage
Fires for every new message in a subscribed thread. Once subscribed, all messages (including @-mentions) route here instead of onNewMention.
bot.onSubscribedMessage(async (thread, message) => {
if (message.isMention) {
// User @-mentioned us in a thread we're already watching
}
await thread.post(`Got: ${message.text}`);
});onNewMessage
Fires for messages matching a regex pattern in unsubscribed threads.
bot.onNewMessage(/^!help/i, async (thread, message) => {
await thread.post("Available commands: !help, !status");
});Prop
Type
onReaction
Fires when a user adds or removes an emoji reaction.
import { emoji } from "chat";
// Filter to specific emoji
bot.onReaction([emoji.thumbs_up, emoji.heart], async (event) => {
if (event.added) {
await event.thread.post(`Thanks for the ${event.emoji}!`);
}
});
// Handle all reactions
bot.onReaction(async (event) => { /* ... */ });Prop
Type
onAction
Fires when a user clicks a button or selects an option in a card.
// Single action
bot.onAction("approve", async (event) => {
await event.thread.post("Approved!");
});
// Multiple actions
bot.onAction(["approve", "reject"], async (event) => { /* ... */ });
// All actions
bot.onAction(async (event) => { /* ... */ });Prop
Type
onModalSubmit
Fires when a user submits a modal form.
bot.onModalSubmit("feedback", async (event) => {
const comment = event.values.comment;
if (event.relatedThread) {
await event.relatedThread.post(`Feedback: ${comment}`);
}
});Prop
Type
Returns ModalResponse | undefined to control the modal after submission:
{ action: "close" }— close the modal{ action: "errors", errors: { fieldId: "message" } }— show validation errors{ action: "update", modal: ModalElement }— replace the modal content{ action: "push", modal: ModalElement }— push a new modal view onto the stack
onSlashCommand
Fires when a user invokes a /command in the message composer. Currently supported on Slack.
// Specific command
bot.onSlashCommand("/status", async (event) => {
await event.channel.post("All systems operational!");
});
// Multiple commands
bot.onSlashCommand(["/help", "/info"], async (event) => {
await event.channel.post(`You invoked ${event.command}`);
});
// Catch-all
bot.onSlashCommand(async (event) => {
console.log(`${event.command} ${event.text}`);
});Prop
Type
onModalClose
Fires when a user closes a modal (requires notifyOnClose: true on the modal).
bot.onModalClose("feedback", async (event) => { /* ... */ });onAssistantThreadStarted
Fires when a user opens a new assistant thread (Slack Assistants API). Use this to set suggested prompts, show a status indicator, or send an initial greeting.
bot.onAssistantThreadStarted(async (event) => {
const slack = bot.getAdapter("slack") as SlackAdapter;
await slack.setSuggestedPrompts(event.channelId, event.threadTs, [
{ title: "Get started", message: "What can you help me with?" },
]);
});Prop
Type
onAssistantContextChanged
Fires when a user navigates to a different channel while the assistant panel is open (Slack Assistants API). Use this to update suggested prompts or context based on the new channel.
bot.onAssistantContextChanged(async (event) => {
const slack = bot.getAdapter("slack") as SlackAdapter;
await slack.setAssistantStatus(event.channelId, event.threadTs, "Updating context...");
});The event shape is identical to onAssistantThreadStarted.
onAppHomeOpened
Fires when a user opens the bot's Home tab in Slack. Use this to publish a dynamic Home tab view.
bot.onAppHomeOpened(async (event) => {
const slack = bot.getAdapter("slack") as SlackAdapter;
await slack.publishHomeView(event.userId, {
type: "home",
blocks: [{ type: "section", text: { type: "mrkdwn", text: "Welcome!" } }],
});
});Prop
Type
Utility methods
webhooks
Type-safe webhook handlers keyed by adapter name. Pass these to your HTTP route handler.
bot.webhooks.slack(request, { waitUntil });
bot.webhooks.teams(request, { waitUntil });getAdapter
Get an adapter instance by name.
const slack = bot.getAdapter("slack");openDM
Open a direct message thread with a user.
const dm = await bot.openDM("U123456");
await dm.post("Hello via DM!");
// Or with an Author object
const dm = await bot.openDM(message.author);thread
Get a Thread handle by its thread ID. Useful for posting to threads outside of webhook contexts (e.g. cron jobs, external triggers).
const thread = bot.thread("slack:C123ABC:1234567890.123456");
await thread.post("Hello from a cron job!");channel
Get a Channel by its channel ID.
const channel = bot.channel("slack:C123ABC");
for await (const msg of channel.messages) {
console.log(msg.text);
}initialize / shutdown
Manually manage the lifecycle. Initialization happens automatically on the first webhook, but you can call it explicitly for non-webhook use cases.
await bot.initialize();
// ... do work ...
await bot.shutdown();During shutdown, the SDK calls the optional disconnect() method on each adapter before disconnecting the state adapter. This lets adapters clean up platform connections, close WebSockets, or tear down subscriptions. If any adapter's disconnect() fails, the remaining adapters and state adapter still disconnect gracefully.
reviver
Get a JSON.parse reviver that deserializes Thread and Message objects from workflow payloads.
const data = JSON.parse(payload, bot.reviver());
await data.thread.post("Hello from workflow!");There is also a standalone reviver export that works without a Chat instance. This is useful in Vercel Workflow functions where importing the full Chat instance (with its adapter dependencies) is not possible:
import { reviver } from "chat";
const data = JSON.parse(payload, reviver) as { thread: Thread; message: Message };The standalone reviver uses lazy adapter resolution - the adapter is looked up from the Chat singleton when first accessed. Call chat.registerSingleton() before using thread methods like post() (typically inside a "use step" function).