Build a Slack Agent
This guide will show you how to build and deploy an AI-powered Slack bot on Cloudflare Workers that can:
- Respond to direct messages
- Reply when mentioned in channels
- Maintain conversation context in threads
- Use AI to generate intelligent responses
Your Slack Agent will be a multi-tenant application, meaning a single deployment can serve multiple Slack workspaces. Each workspace gets its own isolated agent instance with dedicated storage, powered by the Agents SDK.
You can view the full code for this example here ↗.
Before you begin, you will need:
- A Cloudflare account ↗
- Node.js ↗ installed (v18 or later)
- A Slack workspace ↗ where you have permission to install apps
- An OpenAI API key ↗ (or another LLM provider)
First, create a new Slack App that your agent will use to interact with Slack:
- Go to api.slack.com/apps ↗ and select Create New App.
- Select From scratch.
- Give your app a name (for example, "My AI Assistant") and select your workspace.
- Select Create App.
In your Slack App settings, go to OAuth & Permissions and add the following Bot Token Scopes:
chat:write— Send messages as the botchat:write.public— Send messages to channels without joiningchannels:history— View messages in public channelsapp_mentions:read— Receive mentionsim:write— Send direct messagesim:history— View direct message history
You will later configure the Event Subscriptions URL after deploying your agent. But for now, go to Event Subscriptions in your Slack App settings and prepare to enable it.
Subscribe to the following bot events:
app_mention— When the bot is @mentionedmessage.im— Direct messages to the bot
Do not enable it yet. You will enable it after deployment.
From your Slack App settings, collect these values:
- Basic Information > App Credentials:
- Client ID
- Client Secret
- Signing Secret
Keep these handy — you will need them in the next step.
- Create a new project for your Slack Agent:
npm create cloudflare@latest -- my-slack-agentyarn create cloudflare my-slack-agentpnpm create cloudflare@latest my-slack-agent- Navigate into your project:
cd my-slack-agent- Install the required dependencies:
npm install agents openai- Create a
.dev.varsfile in your project root for local development secrets:
touch .dev.vars- Add your credentials to
.dev.vars:
SLACK_CLIENT_ID="your-slack-client-id"SLACK_CLIENT_SECRET="your-slack-client-secret"SLACK_SIGNING_SECRET="your-slack-signing-secret"OPENAI_API_KEY="your-openai-api-key"OPENAI_BASE_URL="https://gateway.ai.cloudflare.com/v1/YOUR_ACCOUNT_ID/YOUR_GATEWAY/openai"- Update your
wrangler.jsoncto configure your Agent:
{ "$schema": "./node_modules/wrangler/config-schema.json", "name": "my-slack-agent", "main": "src/index.ts", "compatibility_date": "2024-01-01", "durable_objects": { "bindings": [ { "name": "MyAgent", "class_name": "MyAgent", "script_name": "my-slack-agent" } ] }, "migrations": [ { "tag": "v1", "new_classes": [ "MyAgent" ] } ]}name = "my-slack-agent"main = "src/index.ts"compatibility_date = "2024-01-01"
[[durable_objects.bindings]]name = "MyAgent"class_name = "MyAgent"script_name = "my-slack-agent"
[[migrations]]tag = "v1"new_classes = ["MyAgent"]-
First, create the base
SlackAgentclass atsrc/slack.ts. This class handles OAuth, request verification, and event routing. You can view the full implementation on GitHub ↗. -
Now create your agent implementation at
src/index.ts:
import { env } from "cloudflare:workers";import { SlackAgent } from "./slack";import { OpenAI } from "openai";
const openai = new OpenAI({ apiKey: env.OPENAI_API_KEY, baseURL: env.OPENAI_BASE_URL});
type SlackMsg = { user?: string; text?: string; ts: string; thread_ts?: string; subtype?: string; bot_id?: string;};
function normalizeForLLM(msgs: SlackMsg[], selfUserId: string) { return msgs.map((m) => { const role = m.user && m.user !== selfUserId ? "user" : "assistant"; const text = (m.text ?? "").replace(/<@([A-Z0-9]+)>/g, "@$1"); return { role, content: text }; });}
export class MyAgent extends SlackAgent { async generateAIReply(conversation: SlackMsg[]) { const selfId = await this.ensureAppUserId(); const messages = normalizeForLLM(conversation, selfId);
const system = `You are a helpful AI assistant in Slack.Be brief, specific, and actionable. If you're unsure, ask a single clarifying question.`;
const input = [{ role: "system", content: system }, ...messages];
const response = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: input });
const msg = response.choices[0].message.content; if (!msg) throw new Error("No message from AI");
return msg; }
async onSlackEvent(event: { type: string } & Record<string, unknown>) { // Ignore bot messages and subtypes (edits, joins, etc.) if (event.bot_id || event.subtype) return;
// Handle direct messages if (event.type === "message") { const e = event as unknown as SlackMsg & { channel: string }; const isDM = (e.channel || "").startsWith("D"); const mentioned = (e.text || "").includes( `<@${await this.ensureAppUserId()}>` );
if (!isDM && !mentioned) return;
const conversation = await this.fetchConversation(e.channel); const content = await this.generateAIReply(conversation); await this.sendMessage(content, { channel: e.channel }); return; }
// Handle @mentions in channels if (event.type === "app_mention") { const e = event as unknown as SlackMsg & { channel: string; text?: string; }; const thread = await this.fetchThread(e.channel, e.thread_ts || e.ts); const content = await this.generateAIReply(thread); await this.sendMessage(content, { channel: e.channel, thread_ts: e.thread_ts || e.ts }); return; } }}
export default MyAgent.listen({ clientId: env.SLACK_CLIENT_ID, clientSecret: env.SLACK_CLIENT_SECRET, slackSigningSecret: env.SLACK_SIGNING_SECRET, scopes: [ "chat:write", "chat:write.public", "channels:history", "app_mentions:read", "im:write", "im:history" ]});Start your development server:
npm run devYour agent is now running at http://localhost:8787.
Now that your agent is running locally, you need to expose it to Slack. Use Cloudflare Tunnel to create a secure tunnel:
npx cloudflared tunnel --url http://localhost:8787This will output a public URL like https://random-subdomain.trycloudflare.com.
Go back to your Slack App settings:
-
Go to Event Subscriptions.
-
Toggle Enable Events to On.
-
Enter your Request URL:
https://random-subdomain.trycloudflare.com/slack. -
Slack will send a verification request — if your agent is running correctly, it should show Verified.
-
Under Subscribe to bot events, add:
app_mentionmessage.im
-
Select Save Changes.
Visit http://localhost:8787/install in your browser. This will redirect you to Slack's authorization page. Select Allow to install the app to your workspace.
After authorization, you should see "Successfully registered!" in your browser.
Open Slack. Then:
- Send a DM to your bot — it should respond with an AI-generated message.
- Mention your bot in a channel (e.g.,
@My AI Assistant hello) — it should reply in a thread.
If everything works, you're ready to deploy to production!
- Before deploying, add your secrets to Cloudflare:
npx wrangler secret put SLACK_CLIENT_IDnpx wrangler secret put SLACK_CLIENT_SECRETnpx wrangler secret put SLACK_SIGNING_SECRETnpx wrangler secret put OPENAI_API_KEYnpx wrangler secret put OPENAI_BASE_URL- Deploy your agent:
npx wrangler deployAfter deploying, you will get a production URL like:
https://my-slack-agent.your-account.workers.devGo back to your Slack App settings:
- Go to Event Subscriptions.
- Update the Request URL to your production URL:
https://my-slack-agent.your-account.workers.dev/slack. - Select Save Changes.
Now that your agent is deployed, you can share it with others:
- Single workspace: Install it via
https://my-slack-agent.your-account.workers.dev/install. - Public distribution: Submit your app to the Slack App Directory ↗.
Each workspace that installs your app will get its own isolated agent instance with dedicated storage.
Your Slack Agent uses Durable Objects to provide isolated, stateful instances for each Slack workspace:
- Each workspace's
team_idis used as the Durable Object ID. - Each agent instance stores its own Slack access token in KV storage.
- Conversations are fetched on-demand from Slack's API.
- All agent logic runs in an isolated, consistent environment.
The agent handles Slack's OAuth 2.0 flow:
- User visits
/install> redirected to Slack authorization. - User selects Allow > Slack redirects to
/acceptwith an authorization code. - Agent exchanges code for access token.
- Agent stores token in the workspace's Durable Object.
When Slack sends an event:
- Request arrives at
/slackendpoint. - Agent verifies the request signature using HMAC-SHA256.
- Agent routes the event to the correct workspace's Durable Object.
onSlackEventmethod processes the event and generates a response.
Update the model in src/index.ts:
const response = await openai.chat.completions.create({ model: "gpt-4o", // or any other model messages: input,});Store conversation history in Durable Object storage:
async storeMessage(channel: string, message: SlackMsg) { const history = await this.ctx.storage.kv.get(`history:${channel}`) || []; history.push(message); await this.ctx.storage.kv.put(`history:${channel}`, history);}Add custom logic in onSlackEvent:
async onSlackEvent(event: { type: string } & Record<string, unknown>) { if (event.type === "message") { const e = event as unknown as SlackMsg & { channel: string };
if (e.text?.includes("help")) { await this.sendMessage("Here's how I can help...", { channel: e.channel }); return; } }
// ... rest of your event handling}Replace OpenAI with Workers AI:
import { Ai } from "@cloudflare/ai";
export class MyAgent extends SlackAgent { async generateAIReply(conversation: SlackMsg[]) { const ai = new Ai(this.ctx.env.AI); const response = await ai.run("@cf/meta/llama-3-8b-instruct", { messages: normalizeForLLM(conversation, await this.ensureAppUserId()), }); return response.response; }}- Add Slack Interactive Components ↗ (buttons, modals)
- Connect your Agent to an MCP server
- Add rate limiting to prevent abuse
- Implement conversation state management
- Use Workers Analytics Engine to track usage
- Add schedules for scheduled tasks
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark