Slack
02 / 03

Building Slack Bots & Apps

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