You can build a Slack bot that browses, reads, uploads, and deletes files in Vercel Blob by combining Chat SDK, AI SDK's ToolLoopAgent, and Files SDK's pre-built tool factory. Chat SDK handles the chat piece, AI SDK runs the agent loop, and Files SDK exposes your Blob store to the agent as a set of approval-gated tools. 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.
This guide will walk you through building a Slack bot with Chat SDK, AI SDK's ToolLoopAgent, and Files SDK's createFileTools factory backed by Vercel Blob. You'll wire up streaming responses, tool calling, and multi-turn conversation history, then configure per-tool approval gates and read-only mode to keep write operations safe in production.
Before you begin, make sure you have:
- Node.js 18 or later
- pnpm (or npm/yarn)
- A Slack workspace where you can install apps
- A Redis instance (local or hosted, such as Upstash)
- A Vercel account with AI Gateway and Vercel Blob
Three SDKs cover three distinct layers:
- Chat SDK is the platform layer. It receives Slack webhooks, normalizes them into events like
onNewMentionandonSubscribedMessage, and streams responses back to Slack's native streaming API. - AI SDK is the reasoning layer.
ToolLoopAgentwraps a language model with tools and runs the loop where the model picks a tool, the SDK executes it, and the result feeds back into the next step until the model finishes. - Files SDK is the storage layer. It presents a single
Filesinterface over Vercel Blob, S3, R2, and other providers, and ships acreateFileToolsfactory that turns that interface into ready-to-use AI SDK tools.
createFileTools returns eight tools, split between four read tools (listFiles, getFileMetadata, downloadFile, getFileUrl) and four write tools (uploadFile, deleteFile, copyFile, signUploadUrl). Write tools require approval by default, while read tools run freely, and you decide how to surface the approval gate to your users.
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.
Learn more about Chat SDK in The Complete Guide to Chat SDK.
Create a new Next.js app and install the Chat SDK, AI SDK, Files SDK, and other related packages:
The chat package is the Chat SDK core, and @chat-adapter/slack and @chat-adapter/state-redis are the Slack platform adapter and Redis state adapter. The ai package is AI SDK, which includes the AI Gateway provider and ToolLoopAgent. files-sdk is the storage SDK, and @vercel/blob is the optional peer dependency required by the Vercel Blob adapter. zod is used for tool input schemas.
Go to api.slack.com/apps, click Create New App, then From a manifest.
Select your workspace and paste this manifest:
After creating the app:
- Go to Install App and install the app to your workspace
- Go to OAuth & Permissions > OAuth Tokens and copy the Bot User OAuth Token
- Go to Basic Information > App Credentials and copy the Signing Secret
You'll replace the request_url placeholders with your real domain after deploying (or a tunnel URL for local testing). The files:read scope is included so the bot can later read files users add to Slack threads.
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 will add BLOB_READ_WRITE_TOKEN to your project’s environment variables for you, so you don’t need to manage the token yourself.
For local development, link your project and pull the token into .env.local with the Vercel CLI:
This writes BLOB_READ_WRITE_TOKEN into .env.local.
Add the remaining environment variables to .env.local:
The Slack adapter reads SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET automatically. The Redis state adapter reads REDIS_URL. AI SDK uses VERCEL_OIDC_TOKEN to authenticate with the Vercel AI Gateway with OIDC authentication. Files SDK will also read BLOB_READ_WRITE_TOKEN automatically when handling file operations.
Create lib/files.ts:
The vercelBlob adapter defaults to access: "public", which matches the most common Blob usage and lets the agent return permanent 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.
Create lib/bot.ts:
A few things are happening here:
createFileTools({ files })returns all eight file tools. Read tools run immediately when the agent calls them; write tools (uploadFile,deleteFile,copyFile,signUploadUrl) are gated by AI SDK's tool approval flow.toAiMessagesconverts the most recent 20 Chat SDK messages into the AI SDKModelMessage[]shape, preserving roles, attachments, and chronological order.result.fullStreamis preferred overtextStreambecause it preserves paragraph breaks between tool-calling steps, which Slack renders cleanly.bot.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.
Create app/api/webhooks/[platform]/route.ts:
This creates an POST /api/webhooks/slack endpoint. The waitUntil option ensures event handlers finish processing after the HTTP response is sent, which is required on Vercel where the function would otherwise terminate as soon as the response returns.
- Start the dev server:
- Expose it with a tunnel:
- Copy the tunnel URL (for example,
https://abc123.ngrok-free.dev) and update both Event Subscriptions and Interactivity Request URLs in your Slack app settings tohttps://abc123.ngrok-free.dev/api/webhooks/slack. - Invite the bot to a channel:
/invite @Files Bot. - @mention the bot and ask it to list files: "Show me what's in the bucket." The agent calls
listFilesand streams the response back into the thread. To test a write operation end-to-end before building an approval flow, temporarily passrequireApproval: falsetocreateFileToolsinlib/bot.tsand ask the bot to "Upload a file called test.txt with the contents 'hello world'."
Link your project and add your environment variables:
Alternatively, add them in the Vercel dashboard under Environment Variables.
Reminder: BLOB_READ_WRITE_TOKEN is already managed by the Blob store connection from step three. You don't need to add it manually.
Then deploy to production:
Update the Event Subscriptions and Interactivity Request URLs in your Slack app settings to your production URL, for example https://my-files-bot.vercel.app/api/webhooks/slack.
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.
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.
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.
If you want to scope a tool's behavior to your domain (for example, "list files in the Acme team folder"), use overrides to patch the 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.
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 means 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.
Check that your Slack app has the app_mentions:read scope and that the Event Subscriptions Request URL is correct. Slack sends a challenge request when you first set the URL, so your server must be running and reachable.
If the agent calls a tool but no result appears, check your server logs for thrown errors. Common causes include a missing BLOB_READ_WRITE_TOKEN, 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.
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.
vercelBlob({ access: "private" }) has no permanent public URL, so getFileUrl (which wraps url()) throws. 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.
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.
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 existing webhook route in src/index.ts already uses a :platform parameter, so Teams webhooks would be handled at /api/webhooks/teams with no additional routing code.
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.
Chat SDK Form BotBuild a bot that helps you address form submissions right from Slack, Teams, and more.
Chat SDK Deploy BotBuild a bot that lets you deploy projects from Slack, with a built-in approval flow.
- Chat SDK AI utilities overview
- Files SDK AI SDK integration
- Files SDK Vercel Blob adapter
- How to build an AI agent for Slack with Chat SDK and AI SDK
- AI SDK agents documentation
- Vercel Blob documentation
- AI Gateway documentation
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.
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.
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.
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.