Slack
Build bots for Slack workspaces with full support for threads, reactions, Agent Sessions, native streaming, scheduled messages, modals, and slash commands.
Create a new bot
Scaffold a Next.js bot with the Slack adapter using create-chat-sdk:
pnpm create chat-sdk@latest my-bot --adapter slack memory -yThe generated app uses the Memory state adapter for local development. To add the adapter to an existing app, start at Install.
Install
pnpm add @chat-adapter/slack @chat-adapter/state-memoryQuick start
The adapter auto-detects SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET from the environment.
For managed credentials and webhook verification, see Vercel Connect.
The memory state adapter keeps subscriptions and locks in process memory, which suits local development. Use Redis or PostgreSQL in production.
Then create the webhook route:
Platform setup
Slack app manifest
Create the app from a manifest at api.slack.com/apps. Replace your-domain.com in both request_url fields with the host that serves your webhook route:
To expose sender email addresses on incoming messages (message.author.email), also add the users:read.email scope. Without it the field is undefined.
Copy credentials
After creating the app, copy these values into your environment:
- Signing Secret →
SLACK_SIGNING_SECRET - Client ID →
SLACK_CLIENT_ID(multi-workspace only) - Client Secret →
SLACK_CLIENT_SECRET(multi-workspace only) - Bot User OAuth Token →
SLACK_BOT_TOKEN(single-workspace only)
Authentication
Vercel Connect
Use Vercel Connect to source the Slack bot token at runtime instead of storing one. The connectSlackAdapter() helper from @vercel/connect/chat wires both a botToken resolver and a webhookVerifier for Connect trigger-forwarded webhooks:
This is equivalent to passing a botToken resolver that calls getToken and a webhookVerifier that validates the Vercel OIDC token Connect attaches. Omit signingSecret / SLACK_SIGNING_SECRET when using it.
Create the connector and attach it to your project with the Vercel CLI. --trigger-path registers the Slack webhook route as the trigger destination, and vercel env pull downloads a development OIDC token for local runs:
Without --trigger-branch, Connect forwards webhooks to your production deployment. See Vercel Connect for the full setup.
Single-workspace mode
For an app installed in one workspace, the adapter reads SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET from the environment:
Token rotation
botToken accepts a function that returns a string or Promise<string>. The adapter calls the resolver for every API call, so it works with Slack token rotation (12-hour TTL) or lazy fetch from a secret manager:
If the resolver is expensive, cache the token inside the resolver.
Multi-workspace OAuth
For apps installed across multiple Slack workspaces, omit botToken and provide OAuth credentials. The adapter resolves tokens dynamically from your state adapter using the team_id (or enterprise_id for Enterprise Grid org-wide installs):
When you pass any auth-related option, such as clientId, the adapter stops reading the other auth fields from environment variables, so one deployment can't mix auth modes by accident.
OAuth callback
Point your Slack OAuth redirect URL to a route that calls handleOAuthCallback:
For Enterprise Grid org-wide installs (is_enterprise_install), Slack returns no team and the installation is keyed by the enterprise ID instead. The returned teamId is always the storage key, so it round-trips with getInstallation and deleteInstallation for both install types; the result also includes enterpriseId and isEnterpriseInstall.
The adapter handles the other Enterprise Grid mechanics automatically: API calls during event handling pass the event's team_id explicitly (required for workspace-scoped methods on org-wide tokens), context_team_id from away-hosted shared channels is echoed back as client_context_team_id, retried event deliveries are deduplicated by event_id, and user caches are scoped per installation.
Using the adapter outside webhooks
During webhook handling, the adapter resolves tokens automatically. Outside that context (cron jobs, background workers), use getInstallation and withBotToken:
withBotToken uses AsyncLocalStorage, so concurrent calls with different tokens stay isolated. In multi-workspace deployments, pass installationId so per-user caches are scoped to that installation and don't bleed across tenants. installationId is the key the installation was stored under: the team_id, or the enterprise_id for org-wide installs.
Message deduplication, locks, queues, concurrency limits, and turn cancellation are scoped to the resolved installation. An org-wide installation shares one scope across its workspaces, because a single token covers all of them. Two installations in the same Slack Connect channel use separate queues and cannot cancel each other's turns. Public thread IDs stay unchanged, and channel members still include external users. This does not establish that recipients are trusted or isolate subscription, thread state, or history keys.
To call bot.abortTurn(threadId) outside webhook handling, use withBotToken with the same installationId. Multi-workspace message dispatch and cancellation throw AuthenticationError when authentication context is unavailable. In multi-workspace mode, a withBotToken call without installationId uses a hashed token scope, which does not match installation-scoped webhook turns and changes when the token rotates. In single-workspace mode, where botToken is configured, these keys are never scoped, including inside withBotToken.
Upgrade chat and @chat-adapter/slack together and restart all workers. Previously queued multi-workspace messages use unscoped keys and are not migrated into installation-scoped queues. Replay pending messages that you need to retain.
Token encryption
Pass a base64-encoded 32-byte key as encryptionKey to encrypt bot tokens at rest using AES-256-GCM:
When encryptionKey is set, setInstallation() encrypts the token before storing and getInstallation() decrypts transparently.
External installation provider
For deployments that manage Slack tokens in an external system, such as Vercel Connect, pass an installationProvider:
The provider is read-only. setInstallation, deleteInstallation, and handleOAuthCallback still write to the internal state adapter.
Configuration
Prop
Type
Webhook mode requires signingSecret or a webhookVerifier. Socket mode requires appToken.
Environment variables
| Variable | Required | Description |
|---|---|---|
SLACK_BOT_TOKEN | Single-workspace mode | Bot User OAuth Token (xoxb-...). |
SLACK_SIGNING_SECRET | Webhook mode, unless you use webhookVerifier or Vercel Connect | Signing secret for webhook verification. |
SLACK_APP_TOKEN | Socket mode | App-level token (xapp-...). |
SLACK_CLIENT_ID | Multi-workspace OAuth | App client ID. |
SLACK_CLIENT_SECRET | Multi-workspace OAuth | App client secret. |
SLACK_ENCRYPTION_KEY | No | Base64-encoded 32-byte key for token encryption. |
SLACK_SOCKET_FORWARDING_SECRET | No | Secret that authenticates forwarded socket mode events. Falls back to appToken. |
SLACK_API_URL | No | Slack Web API base URL override, used when apiUrl isn't set. |
If you pass botToken, clientId, clientSecret, installationProvider, signingSecret, or webhookVerifier in code, the adapter doesn't read SLACK_BOT_TOKEN, SLACK_CLIENT_ID, or SLACK_CLIENT_SECRET from the environment. Passing webhookVerifier also stops it from reading SLACK_SIGNING_SECRET.
Custom webhook verification
Pass webhookVerifier to replace the built-in HMAC check, for example when a proxy or signing layer ahead of your handler already verifies requests:
If both signingSecret and webhookVerifier are set, webhookVerifier wins. When you use webhookVerifier, replay and timestamp protection are your responsibility.
Agents
These features support AI agents on Slack: the Agent messaging experience (agent_view), Agent Sessions, suggested prompts, native streaming, and feedback buttons.
Agent messaging experience
Slack's Agent messaging experience (agent_view manifest mode) supersedes the older assistant_view. New Slack apps can only use agent_view. Enable it on the adapter:
Slack deprecated assistant_view on August 20, 2026 and will retire it in
February 2027. Chat SDK keeps the legacy path available when agentView is
false, but new and migrated apps should use Agent messaging now.
With agentView: true:
onAppHomeOpenedis the DM-open signal (Slack no longer signals DM-open viaassistant_thread_startedunderagent_view), and it fires for either tab. Branch onevent.tab("home"vs"messages") if you also publish a Home view.onAppContextChangedreports the user's active view (see Handling active-view context).getAppContext(message)returns the folded active-view context on a DM message.setSuggestedPrompts(channelId, undefined, prompts)may omit the thread reference, which places the prompts at the top of the agent conversation. AsuggestedPromptsconfig entry is applied automatically on every Messages-tab open.- DM messages are threaded per Slack's model (each user message is a thread root). Threads returned by
openDM()keep working: when the conversation-scoped thread is subscribed, incoming top-level DM messages route to it, soonSubscribedMessageand per-thread state behave as before. - New sessions are titled from the first line of the root message by default. Set
sessionTitle: falseto disable this, or pass a resolver to customize it.
Because bot replies are threaded under each user message, channel-level history (channel.messages, conversations.history) only returns the user's side of a DM conversation. If you build AI conversation history for DMs, use user history instead of channel history. User history records both roles across thread IDs; with channel history, the model never sees its own previous replies.
Add the event subscription and scope to your manifest:
Agent Sessions API
With agentView: true, startTyping() transitions the session to
processing. Slack shows its standard Working indicator and a native stop
button. Chat SDK returns the session to active after posts and streams; use
setSessionStatus() directly for suspended or closed states.
Always pass thread.signal to model APIs. When the user clicks Slack's stop
button, Chat SDK aborts that signal locally and through the configured shared
state adapter, stops rendering the stream, and transitions the session out of
processing.
The SlackAdapter exposes:
| Method | Description |
|---|---|
setSessionStatus(channelId, threadTs, status) | Set processing, active, suspended, or closed |
setAssistantTitle(channelId, threadTs, title) | Rename the agent session |
setSuggestedPrompts(channelId, threadTs, prompts, title?) | Show prompt suggestions |
publishHomeView(userId, view) | Publish a Home tab view |
startTyping(threadId, status?) | Set the agent session to processing, or render a custom status label in the loading UX |
setAssistantStatus and setAssistantTitle remain compatibility methods:
under agentView, clearing status and changing titles use agents.sessions.*.
Custom status text uses the legacy assistant.threads.setStatus compatibility
bridge. setAssistantStatus sends its loadingMessages argument, falls back to
the adapter's loadingMessages config, and otherwise uses the custom status as
the loading message. Under legacy assistant_view, these methods call
assistant.threads.* directly.
Custom labels and native session state are different paths. Use startTyping()
without a custom status when you need native processing state and initiator
attribution. The legacy endpoint cannot receive initiator_user_id, and an
existing native processing indicator can take precedence over custom labels.
Do not rely on custom labels alone to provide native stop-button behavior.
Customize automatic titles with sessionTitle:
Legacy Slack Assistants API
The adapter supports Slack's Assistants API. Register handlers on the Chat instance:
Instead of wiring the handler yourself, you can configure prompts on the adapter. It applies them whenever an assistant or agent thread opens (assistant_thread_started in legacy mode, or a Messages-tab open with agentView enabled):
suggestedPrompts also accepts an async resolver, called per thread-open with the thread context (channelId, userId, threadTs in legacy mode, active-view entities under agentView). Return null to skip a thread. Slack shows at most 4 prompts.
loadingMessages becomes the default for startTyping(threadId) and setAssistantStatus(...) in legacy assistant_view when no explicit status/messages are passed.
The SlackAdapter exposes:
| Method | Description |
|---|---|
setSuggestedPrompts(channelId, threadTs, prompts, title?) | Show prompt suggestions in the thread |
setAssistantStatus(channelId, threadTs, status) | Show a thinking/status indicator |
setAssistantTitle(channelId, threadTs, title) | Set the thread title (shown in History) |
publishHomeView(userId, view) | Publish a Home tab view for a user |
startTyping(threadId, status) | Show a custom loading status (requires assistant:write) |
Add these scopes/events to your manifest:
When streaming in an assistant thread, attach Block Kit elements to the final message via StreamingPlan's endWith option:
Native streaming
Streamed posts (thread.post(asyncIterable)) use Slack's native streaming API (chat.startStream / chat.appendStream / chat.stopStream) whenever the thread has streaming context: any DM thread, or a channel thread where the recipient user/team is known (derived automatically from the incoming message). Structured task_update / plan_update chunks render as native task cards, and plain text renders token-by-token with safe incremental markdown.
Threads without streaming context fall back to post-and-edit (chat.update deltas) automatically. If the workspace rejects the first native call, for example on Slack deployments without the streaming methods such as GovSlack, the adapter falls back to post-and-edit mid-stream without losing content, and skips the native attempt on subsequent streams when the error is permanent (e.g. unknown_method). To skip native streaming entirely:
Slack expires a native stream after roughly five minutes. A reply that streams longer than that is finalized and continued in a new message: once a segment is four minutes old (streamSegmentMaxAgeMs, default 240000; Infinity disables rotation), the adapter rotates at the next paragraph break, or after at most 30 more seconds if none arrives. Across the boundary an open code fence is closed and reopened, a table that continues gets its header repeated, the plan title and any task cards still in progress are replayed so later updates land on them, and with agentView the session stays in processing. Because the work continues in the new message, task cards still open at the boundary are marked complete in the finalized message, and later updates to them appear only in the new message. A list split across the boundary restarts its numbering. The SentMessage returned by thread.post() refers to the last message of the reply. To reach every message, call adapter.stream() directly: its result has messageIds, which lists the messages of the reply in order. Outside DMs, pass recipientUserId and recipientTeamId in its options, or it returns null without streaming. If Slack expires a segment during a long idle gap anyway, the adapter continues in a new message with any text Slack had not confirmed rather than failing the reply.
Feedback buttons
Slack's agent UX guidance recommends native thumbs up/down feedback on agent replies (a context_actions block with a feedback_buttons element). Configure feedbackButtons and the adapter appends them to every streamed reply when the stream finishes:
Clicks dispatch through the regular action flow with the configured actionId (default "message_feedback"). For non-streamed messages, build the same block with the exported buildFeedbackButtonsBlock(options?) helper and attach it via raw blocks. Feedback buttons are skipped when a stream falls back to post-and-edit.
Socket mode
For environments behind firewalls that can't expose public HTTP endpoints, use Slack Socket Mode:
Events that arrive over the socket, or are forwarded from a socket listener, resolve tokens the same way the webhook path does: from botToken in single-workspace mode, or from stored installations by team_id (or enterprise_id for Enterprise Grid org-wide installs). A socket-mode adapter can't run the OAuth install flow itself, though. createSlackAdapter throws if you pass clientId or clientSecret with mode: "socket".
Socket mode on serverless (Vercel)
Socket mode requires a persistent WebSocket. On serverless platforms, a cron job starts a transient socket listener that acks events and forwards them as HTTP requests to your existing webhook endpoint:
Forwarded events are authenticated using socketForwardingSecret (defaults to SLACK_SOCKET_FORWARDING_SECRET, falling back to appToken).
Tables and charts
Card Table elements render as Slack data table blocks, which are paginated and sortable and accept optional caption and pageSize props. Tables that exceed Slack's limits (100 data rows, 20 columns, 10,000 characters across all cells) fall back to ASCII text, and header-only tables render as a plain table block.
Card Chart elements render as Slack data visualization blocks with pie, bar, area, and line chart support:
Charts that violate Slack's constraints (50-character title, 12 segments/series, 20 categories, 20-character labels, one data point per category, at most 2 charts per message) fall back to a text rendering of the data instead of being rejected by the Slack API.
Inbound attachments
Incoming file attachments expose a lazy fetchData(). Downloads go through a guarded fetcher that limits responses to 25 MB, times out after 30 seconds, and, with the default transport, refuses private and internal addresses (including after redirects). The bot token is sent only to trusted Slack origins and never follows a redirect to another host. Set fileTransport to route downloads through a proxy, or override createFileTransport() in a subclass. A custom transport takes over the destination-address policy, so see Egress proxies for what it must enforce.
Egress proxies
Configure each transport your deployment uses. webClientOptions.agent covers Slack Web API calls (including OAuth and all upload phases) and the HTTP/WebSocket connections for both persistent and transient Socket Mode. fetch covers response_url updates and Socket Mode forwarding to your application's webhook. fileTransport covers lazy fetchData() and rehydrated attachments.
For Node.js, install https-proxy-agent and undici in your application. This example assumes a controlled proxy that rejects internal destination addresses and DNS rebinding, including when it resolves CONNECT destinations. A custom file transport replaces the default DNS-pinned transport, which is the only place resolved addresses are checked against the private-range blocklist. Local DNS checks alone cannot enforce the address a remote proxy actually uses, so that policy has to live in the proxy.
The downloader still validates each URL, limits redirects, sends credentials only to trusted Slack origins, rejects HTML login pages, and limits decoded bodies to 25 MB. The 30-second deadline is enforced by the downloader for both the wait for response headers and the body read, so it holds even if the transport ignores the signal. The transport should still honor the signal so the underlying connection is released promptly. The transport must not follow redirects itself. Existing createFileTransport() subclass overrides take precedence over fileTransport.
Socket Mode receives agent, tls, and apiUrl, but only agent reaches the WebSocket itself; tls and apiUrl apply to its HTTP calls. The Slack SDK opens the WebSocket with the agent alone, so a custom CA or other TLS settings for that connection must be configured on the agent. App-token authentication, headers, and retry options remain SDK defaults, since Web API headers are not Socket Mode headers. Configure routing/bypass in your fetch implementation if the forwarded webhook uses an internal application URL. The adapter does not close caller-owned agents or dispatchers; close them when your application shuts down.
Standalone @chat-adapter/slack/api functions have their own options.fetch parameter and do not inherit adapter configuration. Application callbacks, token resolvers, installation providers, and state adapters also own their network configuration. Proxy authentication, CA trust, WebSocket support, and destination policy must be configured for your deployment.
Thread history
Slack returns thread replies oldest-first, so a backward read follows every Slack API page in the thread before it returns the newest messages in chronological order. Each read makes one conversations.replies call per page, even when limit is small. Pass nextCursor to fetch the preceding messages; each of those reads also starts from the beginning of the thread. See the History API reference.
Slack limits new commercially distributed apps outside the Slack Marketplace and new installations of existing apps to one conversations.replies request per minute and 15 messages per page. Existing installations, Marketplace apps, and internal customer-built apps are exempt from these stricter limits. For affected installations, a backward read of a 400-message thread can take about 30 minutes, because @slack/web-api waits out each rate limit by default. Use direction: "forward" to read incrementally through Slack's native pagination.
Direct API client
Access the underlying WebClient from @slack/web-api via .webClient:
Single-workspace mode (with a static botToken or synchronous resolver) returns a client anywhere. Multi-workspace mode requires webhook-handler context or an explicit withBotToken wrapper, and calling .webClient outside either throws.
The previous
.clientgetter still works as a deprecated alias for.webClient.
Low-level APIs
Use the low-level Slack subpaths when your app already owns routing, state, sessions, or workflow execution and only needs the Slack-specific primitives.
| Subpath | Use for |
|---|---|
@chat-adapter/slack/webhook | Request verification, body parsing, Events API payloads, slash commands, interactions, and continuation data |
@chat-adapter/slack/format | Slack mrkdwn tokens, text objects, dates, links, mentions, and simple mrkdwn to Markdown conversion |
@chat-adapter/slack/api | Fetch-based Slack Web API calls, thread replies, views, and files without @slack/web-api |
@chat-adapter/slack/blocks | Runtime-free conversion from simple card objects and input requests to Slack Block Kit |
These subpaths are for custom runtimes. If you want Chat SDK to handle webhook routing, state, subscriptions, and platform normalization, use createSlackAdapter from @chat-adapter/slack.
Webhooks
Slack signs incoming HTTP requests with x-slack-signature and x-slack-request-timestamp. verifySlackRequest reads the request body, verifies the signature with your signing secret, and returns the raw body so you can parse it once.
Slack slash commands and interactions should be acknowledged quickly. Slack documents a 3000 ms acknowledgement window for slash commands, so do slow work in your queue or workflow runtime after returning a 2xx response.
If you do not need direct access to the verified raw body, readSlackWebhook combines verification and parsing:
If your framework already buffered the request body, use verifySlackSignature with the raw body and headers, then pass that same body to parseSlackWebhookBody.
Payloads
parseSlackWebhookBody returns typed payloads:
| Kind | Slack surface |
|---|---|
url_verification | Events API URL verification |
app_mention | App mention events |
direct_message | Direct message events |
slash_command | Slash command form posts |
block_actions | Button, select, and Block Kit action payloads |
block_suggestion | External select suggestion payloads |
view_submission | Modal submissions |
view_closed | Modal close events |
unsupported | Valid Slack payloads not normalized by this helper yet |
Message-like payloads include continuation, which contains provider-native reply context:
This is not a Chat SDK Thread. It is the durable Slack data you need to reply later with @chat-adapter/slack/api.
App mention and direct message payloads also include typed files parsed from Slack file objects. Each file keeps the raw Slack object plus common fields like id, name, mimeType, size, url, and downloadUrl.
Interaction payloads expose convenience fields from Slack's raw payload:
block_actionsincludesactions,messageBlocks,messagePromptBlock,messagePromptText,messageTs,triggerId,responseUrl,user, andcontinuationview_submissionincludescallbackId,privateMetadata,values,responseUrls, anduser
Formatting
Slack uses mrkdwn and special tokens for mentions, channels, dates, and links. The format subpath gives you small helpers for those strings.
The helper surface includes escapeSlackText, unescapeSlackText, createSlackPlainText, createSlackMrkdwn, formatSlackUser, formatSlackChannel, formatSlackUserGroup, formatSlackSpecialMention, formatSlackLink, formatSlackDate, and simple mrkdwn to Markdown normalization.
linkBareSlackMentions only links Slack user IDs like @U123. It does not resolve display names, because Slack mentions are ID-based.
Web API
The API subpath calls Slack Web API methods with fetch. It does not import @slack/web-api.
Use callSlackApi when you need a Slack method that does not have a helper yet:
markdownText maps to the markdown_text field on chat.postMessage and cannot be combined with text or blocks. Use text with blocks when you need fallback text.
The subpath also includes postSlackEphemeral, deleteSlackMessage, resolveSlackBotToken, encodeSlackApiBody, and assertSlackOk.
Use fetchSlackThreadReplies when a custom runtime needs to refresh a thread with conversations.replies:
Use openSlackView to open a modal from an interaction trigger_id:
Files
Slack's current external upload flow uses files.getUploadURLExternal, then uploads bytes to the returned URL, then calls files.completeUploadExternal.
Pass blocks instead of initialComment to share the files with Block Kit blocks. Slack renders the files below the blocks. Passing both throws a TypeError, since Slack would silently ignore blocks.
Use fetchSlackFile for private Slack file URLs that require bearer token authorization.
Blocks
The blocks subpath converts simple card objects into Slack Block Kit without importing the full chat JSX runtime.
It exports cardToSlackBlocks, cardToBlockKit, cardToSlackFallbackText, cardToFallbackText, and convertSlackEmojiPlaceholders.
Use the full Chat SDK card JSX when you want cross-platform rendering. Use @chat-adapter/slack/blocks when you are building a Slack-only runtime and want Block Kit output directly.
Card children support the same element types as the cross-platform card model, including table (rendered as a paginated, sortable data table block with optional caption and pageSize) and chart (rendered as a data visualization block):
Tables and charts that exceed Slack limits (100 data rows / 20 columns / 10,000 characters for tables; 12 segments or series, 20 categories, 20-character labels, 50-character titles, and 2 charts per message for charts) fall back to a text rendering instead of being rejected by the Slack API.
The blocks subpath also includes small input request helpers for Slack-only runtimes:
Set display: "radio" for radio buttons, or display: "select" for a static select menu. Set allowFreeform: true to add a "Type your answer" button next to the provided options.
For freeform answers, use buildSlackFreeformView with openSlackView, then read the submitted value from payload.values with parseSlackFreeformValue.
Import boundaries
The low-level Slack subpaths are designed to avoid the full runtime import graph:
- no
chatimport - no
@chat-adapter/sharedimport - no
@slack/web-apiimport - no
@slack/socket-modeimport
The package still installs the full Slack adapter dependencies. The subpaths keep your source and bundle imports clean, but they are not a package-size split.
Feature support
Messaging
| Feature | Supported |
|---|---|
| Post message | |
| Message replies | |
| Edit message | |
| Delete message | |
| File uploads | |
| Streaming | Native |
| Scheduled messages | Native |
Rich content
| Feature | Supported |
|---|---|
| Card format | Block Kit |
| Buttons | |
| Link buttons | |
| Select menus | |
| Tables | Block Kit |
| Charts | Block Kit |
| Fields | |
| Images in cards | |
| Modals |
Conversations
| Feature | Supported |
|---|---|
| Slash commands | |
| Mentions | |
| Add reactions | |
| Remove reactions | |
| Typing indicator | |
| Mark as read | |
| Message edit events | |
| Message delete events | |
| DMs | |
| Ephemeral messages | Native |
| User lookup | |
| Parent subject | |
| Native client | |
| Custom API endpoint |
Message history
| Feature | Supported |
|---|---|
| Fetch messages | |
| Fetch single message | |
| Fetch thread info | |
| Fetch channel messages | |
| List threads | |
| Fetch channel info | |
| Post channel message |
Resources
- How to build an AI agent for Slack with Chat SDK and AI SDK: Build a Slack AI agent using Chat SDK, AI SDK's ToolLoopAgent, and Vercel AI Gateway. Covers project setup, tool definitions, streaming responses, deployment to Vercel, and scaling tool selection with toolpick.
- How to build a Slack bot that manages files in Vercel Blob: Build a Slack bot that lists, reads, uploads, and deletes files in Vercel Blob through tool calls. Uses Chat SDK, AI SDK's ToolLoopAgent, and Files SDK's
createFileToolsfactory with approval-gated write tools and a read-only mode. - How to build a Slack bot with Next.js and Redis: Walks through building a Slack bot with Next.js, covering project setup, Slack app configuration, event handling, interactive features, and deployment.
See all guides and templates on the resources page.