Building Slack Bots & Apps
Bolt Framework (Official SDK)
Bolt is Slack's official SDK for building apps in Node.js, Python, and Java. It handles OAuth, event routing, and middleware automatically.
import { App } from '@slack/bolt'
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
// Socket Mode (no public URL needed — great for development)
socketMode: true,
appToken: process.env.SLACK_APP_TOKEN,
})
// Listen to messages mentioning the bot
app.message('hello', async ({ message, say }) => {
await say(`Hey <@${message.user}>!`)
})
// Handle a slash command
app.command('/todo', async ({ command, ack, respond }) => {
await ack() // must acknowledge within 3s
await respond(`Added: ${command.text}`)
})
// Handle button click
app.action('approve_button', async ({ body, ack, client }) => {
await ack()
await client.chat.update({
channel: body.channel!.id,
ts: body.message!.ts,
text: 'Request approved!',
})
})
await app.start(3000)Events API
Subscribe to Slack events (messages, reactions, user changes) via HTTP endpoint or Socket Mode. Configure subscriptions in your app's Event Subscriptions settings.
// Common event subscriptions
app.event('app_mention', async ({ event, client }) => {
// Bot was @mentioned
await client.chat.postMessage({
channel: event.channel,
thread_ts: event.ts, // reply in thread
text: `You mentioned me: ${event.text}`,
})
})
app.event('reaction_added', async ({ event }) => {
console.log(`${event.user} reacted with :${event.reaction}: on message ${event.item.ts}`)
})
app.event('message', async ({ event }) => {
// Filter: only DMs
if (event.channel_type === 'im') {
console.log('DM received:', event.text)
}
})Modals & Shortcuts
// Open a modal from a shortcut
app.shortcut('create_ticket', async ({ shortcut, ack, client }) => {
await ack()
await client.views.open({
trigger_id: shortcut.trigger_id,
view: {
type: 'modal',
callback_id: 'ticket_modal',
title: { type: 'plain_text', text: 'Create Ticket' },
submit: { type: 'plain_text', text: 'Create' },
blocks: [
{
type: 'input',
block_id: 'title',
label: { type: 'plain_text', text: 'Title' },
element: { type: 'plain_text_input', action_id: 'title_input' }
},
{
type: 'input',
block_id: 'priority',
label: { type: 'plain_text', text: 'Priority' },
element: {
type: 'static_select',
action_id: 'priority_select',
options: [
{ text: { type: 'plain_text', text: 'High' }, value: 'high' },
{ text: { type: 'plain_text', text: 'Low' }, value: 'low' },
]
}
}
]
}
})
})
// Handle modal submission
app.view('ticket_modal', async ({ view, ack, client, body }) => {
await ack()
const title = view.state.values.title.title_input.value
const priority = view.state.values.priority.priority_select.selected_option?.value
// Create ticket...
})OAuth & App Distribution
// Bolt handles OAuth automatically with installationStore
const app = new App({
signingSecret: process.env.SLACK_SIGNING_SECRET,
clientId: process.env.SLACK_CLIENT_ID,
clientSecret: process.env.SLACK_CLIENT_SECRET,
stateSecret: 'my-state-secret',
scopes: ['channels:read', 'chat:write', 'commands'],
installationStore: {
storeInstallation: async (installation) => {
// Save installation to DB
await db.saveInstallation(installation)
},
fetchInstallation: async (installQuery) => {
return await db.getInstallation(installQuery.teamId)
},
},
})Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free