You can build a Slack bot that browses, reads, uploads, and deletes files in Vercel Blob by combining three SDKs. Chat SDK handles the Slack integration, AI SDK's ToolLoopAgent runs the agent loop, and Files SDK exposes your Blob store to the agent as a set of approval-gated tools. Vercel Connect supplies the Slack credentials at runtime and forwards Slack events to your project. The result is a chat-first interface to your object storage, with read tools that run freely and write tools that prompt for approval by default.
You'll scaffold the bot with the create-chat-sdk CLI, which generates the Connect wiring for you, then create a Slack connector, connect a Blob store, and add an agent backed by Files SDK's createFileTools factory. From there, you'll configure per-tool approval gates and read-only mode to keep write operations safe in production.
Copy link to headingPrerequisites
Before you begin, make sure you have:
- Node.js 20+ and a package manager (e.g., pnpm)
- A Slack workspace where you can install an app
- Access to a Vercel team and project with Vercel Connect enabled
- Vercel CLI installed (
npm i -g vercel) - A Vercel account with AI Gateway and Vercel Blob
Copy link to headingHow it works
Each SDK has one job:
- Chat SDK receives Slack webhooks, normalizes them into events like
onNewMentionandonSubscribedMessage, and streams responses back through Slack's native streaming API. - AI SDK provides
ToolLoopAgent, which wraps a language model with tools and runs the loop: the model picks a tool, the SDK executes it, and the result feeds into the next step until the model finishes. - Files SDK presents a
Filesinterface over your Blob store and ships acreateFileToolsfactory that turns it into ready-to-use AI SDK tools. The factory returns eight tools: four reads (listFiles,getFileMetadata,downloadFile,getFileUrl) that run freely and four writes (uploadFile,deleteFile,copyFile,signUploadUrl) that require approval by default.
Vercel Connect handles the Slack credentials, in both directions:
- Outbound: the
connectSlackAdapterhelper from@vercel/connect/chatsupplies abotTokenresolver that fetches a fresh, short-lived Slack token per API request via Connect'sgetToken. - Inbound: Connect verifies each Slack event, then forwards it to a trigger destination you register on the connector. For this bot, that destination is your project at
/api/webhooks/slack. The helper'swebhookVerifierconfirms each forwarded event carries a valid Vercel OIDC token.
Because OIDC verification replaces Slack's signed timestamp check, freshness relies on the short-lived token's expiry, and Connect has no built-in delivery de-duplication. Keep your webhook handlers idempotent.
Chat SDK accepts any AsyncIterable<string> as a message, so the agent's fullStream flows straight into thread.post() for real-time streaming in Slack.
Copy link to headingSteps
Copy link to heading1. Scaffold the project with create-chat-sdk
The create-chat-sdk CLI generates the Chat SDK skeleton with a single command. Pass the Slack platform adapter, Redis state adapter, and --connect flag to authenticate with Vercel Connect:
If you use npm, add the -- separator so npm forwards the flags to the CLI instead of consuming them itself:
The CLI generates:
- The
Chatconfiguration inlib/bot.ts. Because you passed--connect, it spreads theconnectSlackAdapterhelper into the Slack adapter and adds@vercel/connectto your dependencies. - A dynamic webhook route that becomes
POST /api/webhooks/slack. - Starter handlers for mentions and thread replies, which you'll extend in step six.
The scaffolded app is webhook-only. Add the AI SDK and Files SDK packages yourself, since the agent is your code rather than part of the scaffold:
The ai package includes ToolLoopAgent and the AI Gateway provider. zod defines tool input schemas. files-sdk is the storage SDK, and @vercel/blob is the peer dependency its Vercel Blob adapter requires.
Copy link to heading2. Create a Slack connector with Vercel Connect
Vercel Connect creates and manages the Slack app for you. You don't register an app at api.slack.com, write a manifest, or copy any credentials.
Open the Connect page on your team's dashboard, then click Create Connector. Once you've done that:
- Choose Slack as the provider.
- Select your Slack workspace and name the app (e.g.,
files-bot). If you haven't connected a Slack workspace yet, connect and authorize one first, then return to the Connect page. Keep Triggers enabled so Slack events reach your project. - Open Advanced and set:
- Bot Scopes:
chat:write,channels:history,channels:read,groups:history,im:history,mpim:history,reactions:write,users:read, andfiles:read. Thefiles:readscope lets the bot read files users add to Slack threads. - Trigger Event Types:
app_mention, plus the message events for the surfaces the bot supports (message.channels,message.groups,message.im,message.mpim), so both new mentions and follow-up replies work.
- Bot Scopes:
- Click Create Slack Connector, then Install to your Slack Workspace.
- In the connector's settings, link it to your project, select the environments it applies to (e.g., production), and register your project as a trigger destination with the path
/api/webhooks/slack, the route the CLI generated in step one.
You can also do this from the CLI. Create the connector with trigger forwarding enabled, then attach your project and register the trigger destination:
Because Connect manages the Slack app, Slack delivers events to Connect's intake URL (shown in the connector settings), not directly to your deployment. Connect verifies Slack events, then forwards them to the trigger destination you registered.
Finally, add the connector UID to your project's environment variables so deployments can resolve it:
Replace slack/files-bot with your connector UID from the Connect dashboard or vercel connect list.
Copy link to heading3. Create a Vercel Blob store
In your Vercel dashboard, open Storage, click Create Database, and create a new Blob store. Connect it to the project you'll be deploying to.
Vercel adds BLOB_STORE_ID to your project's environment variables, which the SDK pairs with the deployment's short-lived VERCEL_OIDC_TOKEN to authenticate via OIDC. No static Blob token to manage.
Copy link to heading4. Link your project and provision Redis
Your bot uses Redis for thread subscriptions and distributed locking.
Link your project, then provision Upstash Redis with the Vercel CLI:
vercel integration add provisions a database, connects it to your project, and adds its connection variables to your environment. Other providers are also supported in the Vercel Marketplace, including Redis.
To use the dashboard instead, create the database from your project's Storage page.
Deployments also populate VERCEL_OIDC_TOKEN automatically. AI SDK uses it to authenticate requests to the AI Gateway. The Vercel Connect SDK @vercel/connect and Files SDK read it too, so there's no API key to generate or store.
Copy link to heading5. Configure Files SDK with the Vercel Blob adapter
Create lib/files.ts:
The vercelBlob adapter defaults to access: "public", which matches the most common Blob usage and lets the agent return CDN URLs from getFileUrl.
For private buckets, pass vercelBlob({ access: "private" }), which routes uploads through Vercel's private mode and reads through the API instead of a public URL. With private access, getFileUrl throws because no permanent URL exists; use downloadFile instead.
Copy link to heading6. Add the agent to the generated bot
The CLI already generated lib/bot.ts with the Connect wiring and starter handlers. Update it to create a ToolLoopAgent backed by createFileTools and stream its responses from the mention and thread handlers:
createFileTools({ files }) returns all eight file tools. Read tools run as soon as the agent calls them, while write tools are gated by AI SDK's tool approval flow.
The connectSlackAdapter spread is the Connect wiring the CLI generated.
It returns two fields:
- A
botTokenresolver. The adapter calls it on each Slack API request, and it fetches a fresh, short-lived token via Connect'sgetToken. The token's subject is pinned to{ type: "app" }, so the bot acts as the application itself, and the scopes you set on the connector determine what the token can do. - A
webhookVerifierthat validates the Vercel OIDC token Connect attaches to each forwarded event. The default verifier matches the deployment's own project and environment (projectIddefaults toVERCEL_PROJECT_ID, andenvironmenttoVERCEL_TARGET_ENV, thenVERCEL_ENV), fails closed when those values are missing, and pins the issuer tohttps://oidc.vercel.com. It also replaces Slack's 5-minute timestamp check.
The helper accepts optional getToken parameters as a second argument (everything except subject), such as scopes or validityBufferMs, if you need to narrow or tune token requests.
The event handlers work like this:
onNewMentionfires the first time someone @mentions the bot in a channel.thread.subscribe()opts the thread into futureonSubscribedMessageevents, so the bot keeps responding without further mentions.onSubscribedMessagefetches the 20 most recent thread messages and converts them withtoAiMessagesinto the AI SDKModelMessage[]shape, preserving roles, attachments, and chronological order, so the agent has conversation context.- Both handlers post
result.fullStreamrather thantextStreambecause it preserves paragraph breaks between tool-calling steps, which Slack renders cleanly.
You don't need to modify the generated webhook route. With trigger forwarding enabled, Connect POSTs verified Slack payloads to /api/webhooks/slack, the webhookVerifier runs before the adapter parses the body, and the route's waitUntil keeps your handlers running after the HTTP response returns, which is required on serverless platforms where the function would otherwise terminate early.
Copy link to headingCustom webhook verification (optional)
The default verifier accepts only the deployment's own environment. If you forward events to more than one environment, build a verifier with createConnectWebhookVerifier and override the field:
Avoid hardcoding environment: "production" unless you only forward to production, since that would reject preview and development deployments.
Copy link to heading7. Test the agent
Slack sends events to Connect, which forwards them to a deployed Vercel project rather than to your machine. You test the full round trip against a preview or development deployment, with no local tunnel to spin up.
- Deploy a preview build to receive the Slack events:
2. In the connector's settings, make sure that deployment's environment is linked and registered as the trigger destination at /api/webhooks/slack. The default verifier only accepts tokens for the deployment's own environment, so the connector must forward to the environment you're testing.
3. Invite the bot to a channel: /invite @Files Bot.
4. @mention the bot and ask it to list files: "Show me what's in the bucket." The agent calls listFiles and streams the response back into the thread. To test a write operation end-to-end before building an approval flow, temporarily pass requireApproval: false to createFileTools, redeploy, and ask the bot to "Upload a file called test.txt with the contents 'hello world'."
Copy link to heading8. Deploy to production
Once you've tested the bot, deploy it to production:
Confirm the production environment is linked to the connector and registered as a trigger destination at /api/webhooks/slack. Redis comes from the Upstash integration, Blob authentication from OIDC via the store connection, and Slack credentials from Connect at runtime, so SLACK_CONNECTOR is the only variable you add to the project yourself.
Copy link to headingConfiguring approval and read-only mode
The default createFileTools({ files }) gates every write tool with approval and leaves reads open. That's a reasonable default, but you'll often want to tune it.
Copy link to headingGranular approval
Pass an object to requireApproval to opt individual tools in or out:
Unspecified entries default to true, so it's safe to opt in only the cases you trust. In the example above, the agent can upload and copy without prompting, but still needs approval for deletes and pre-signed upload URLs.
For a production-grade approval handler that pauses the workflow in Slack until a human clicks Approve or Deny, see Human-in-the-Loop with Chat SDK and Workflow SDK. The same pattern wraps any write tool from createFileTools.
Copy link to headingRead-only mode
For a bot that should only browse and summarize files, pass readOnly: true:
Read-only mode drops every write tool from the toolset, so approval configuration becomes irrelevant. This is useful when the bot's job is to find a file and hand the user a download URL rather than mutate the bucket.
Copy link to headingTightening descriptions per tool
To scope a tool's behavior to your domain, use overrides to patch its description without touching the underlying implementation:
execute, inputSchema, and outputSchema are intentionally not overridable. Override descriptions to improve tool selection, override titles for clearer approval UIs, and let the SDK keep ownership of the I/O contract.
Copy link to headingReading Slack uploads with toAiMessages
When users upload files to a Slack thread, toAiMessages automatically includes them in the AI SDK message stream. Images become image parts and supported text files (JSON, XML, YAML, plain text) become file parts, both with base64 data. Video and audio attachments are skipped, with a console.warn by default. This relies on the files:read scope you set on the connector in step two.
A user can drag a CSV into the thread and ask, "Upload this to reports/q4.csv," and the agent will see the file contents in its message history and can call uploadFile with that content. No extra wiring needed.
To customize how unsupported attachments are handled, pass onUnsupportedAttachment:
PDFs and other unrecognized MIME types are silently skipped. If you need to handle them, fetch the raw attachment via attachment.fetchData() in your handler and route it directly to files.upload() outside the agent loop.
Copy link to headingTroubleshooting
Copy link to headingThe bot doesn't respond to mentions
Check that your Slack connector has trigger forwarding enabled and that your project is registered as a trigger destination with the correct path (/api/webhooks/slack). Confirm the connector is installed in your workspace and that its Trigger Event Types include app_mention and the relevant message events. You can review all of this in the connector's settings. Verify deployment logs on /api/webhooks/slack for 401s before debugging token issues.
Copy link to headingToken requests fail or return unauthorized
Make sure the project is linked (vercel link) and that the connector is linked to it for the current environment. Confirm SLACK_CONNECTOR is set to the correct connector UID, the connector is installed in your workspace, and the scopes the bot uses are enabled on the connector.
Copy link to headingWebhook returns 401
- Confirm
connectSlackAdapteris spread intocreateSlackAdapter, so the helper'swebhookVerifieris active. - Confirm OIDC Federation is enabled on the project. The verifier fails closed, so if
VERCEL_PROJECT_IDor the other required environment variables are missing, every request is rejected. - Confirm the request is coming from Connect (trigger destination configured), not from Slack hitting your app directly.
- If you forward to multiple environments, the default verifier rejects tokens from environments other than the deployment's own. Override it with
createConnectWebhookVerifier({ environment: [...] })as shown above.
Copy link to headingMissing Authorization bearer token
The event reached your app without Connect forwarding, meaning the trigger destination isn't registered or points to the wrong path. Fix the trigger path to /api/webhooks/slack and link the correct environment.
Copy link to headingDuplicate replies or duplicate writes
OIDC verification replaces Slack's native signature and timestamp check, and Connect has no built-in delivery de-duplication. If a forwarded event is delivered more than once, your handlers run multiple times, which for this bot can mean a duplicate write against your Blob store. Keep webhook handlers idempotent, for example by tracking processed event IDs in Redis.
Copy link to headingTool calls fail silently
If the agent calls a tool but no result appears, check your server logs for thrown errors. Common causes include a Blob store not connected to the project, an invalid file key, or a vercelBlob({ access: "private" }) adapter trying to call getFileUrl. AI SDK surfaces tool execution errors back to the model, which may attempt to recover; add explicit error handling in your tools if you need to control how the model sees the failure.
Copy link to headingWrite operations are always rejected
By default, write tools require approval. Until you build an approval handler that resolves these requests, every write call will be denied. For development, pass requireApproval: false to disable the gate, or requireApproval: { deleteFile: true } to leave only the most destructive operations gated.
Copy link to headinggetFileUrl throws on private blobs
vercelBlob({ access: "private" }) has no permanent public URL, so getFileUrl (which wraps url()) throws an error. Use downloadFile to fetch private blob contents through the API instead. If you need both public and private blobs in the same bot, construct two Files instances with different adapters and route the agent to the right one through separate tools.
Copy link to headingThread history grows too large
For long-running threads, the conversation history can exceed the model's context window. Limit the number of messages passed to the agent (the example above uses limit: 20) or summarize older messages in a separate step.
Copy link to headingHow to add Teams, Discord, or other platforms
Chat SDK supports multiple platforms from a single codebase. The event handlers and agent logic you've already defined work identically across all of them, since the SDK normalizes messages, threads, and reactions into a consistent format.
To add Microsoft Teams or another platform, register an additional adapter:
The webhook route already uses a :platform parameter, so Teams webhooks are handled at /api/webhooks/teams with no additional routing code. Connect trigger forwarding currently supports Slack, GitHub, and Linear, so adapters for other platforms manage their own credentials and receive webhooks directly.
Streaming behavior varies by platform. Slack uses its native streaming API for smooth real-time updates, while Teams, Discord, and Google Chat fall back to a post-then-edit pattern that throttles updates to avoid rate limits. You can adjust the update interval with the streamingUpdateIntervalMs option when creating your Chat instance.
See the Chat SDK adapter directory for the full list of supported platforms.
Copy link to headingRelated resources and next steps
- Vercel Connect overview
- Vercel Connect quickstart guide
- Vercel Connect SDK reference
- Chat SDK CLI reference
- Chat SDK Vercel Connect integration docs
- Chat SDK AI utilities overview
- Files SDK Vercel Blob adapter
- AI SDK agents documentation
- Vercel Blob documentation
- AI Gateway documentation
Build a software factory with Vercel Connect
Ship a software factory in minutes, with GitHub and Linear connectors provisioned for you from the first deployment.
Copy link to headingFAQ
Is Chat SDK free?
Yes, Chat SDK is free and open source. You can install it from npm, use it in commercial projects, and contribute to its development without any licensing cost. The official platform adapters published under @chat-adapter/* are also free to use. You will still need to pay for any third-party services your bot relies on, such as your chat platform's API tier, your AI model provider, and your Redis or Postgres host.
Can I use any number of adapters?
Yes, you can register as many adapters as you need on a single Chat instance. Add each adapter to the adapters object when you create your bot, and the same handlers will run for events from every connected platform. This is the main reason to use Chat SDK: you write your bot logic once and it works across Slack, Teams, Discord, Linear, and any other platform you add later, without changing the rest of your code.
What frameworks does Chat SDK support?
Chat SDK supports nearly any JavaScript or TypeScript framework, so you can add it to your existing codebase without changing your stack. It works with Next.js, Nuxt, Hono, SvelteKit, Express, and other server frameworks that can handle HTTP requests. Each platform adapter exposes a webhook handler (such as bot.webhooks.slack) that you wire into your framework's routing, whether that is a Next.js route handler, a Hono route, a Nuxt server route, or something else.
Does Chat SDK only work on Vercel?
No, Chat SDK works anywhere you can run a Node.js or TypeScript server. It is framework-agnostic and runs on Next.js, Nuxt, Hono, and other frameworks, so you can deploy it to Vercel, AWS, Netlify, Cloudflare Workers, your own infrastructure, or any other host. Vercel maintains the SDK and the official adapters, but there is no requirement to deploy on Vercel to use it.