From 1051f0f6dbc7d7e4ad54d56608c1bc5c1936ef95 Mon Sep 17 00:00:00 2001 From: Shane DeWael Date: Thu, 16 May 2019 12:23:46 -0700 Subject: [PATCH] remove site --- docs/_site/concepts.html | 1090 ---------------------- docs/_site/tutorial/getting-started.html | 496 ---------- 2 files changed, 1586 deletions(-) delete mode 100644 docs/_site/concepts.html delete mode 100644 docs/_site/tutorial/getting-started.html diff --git a/docs/_site/concepts.html b/docs/_site/concepts.html deleted file mode 100644 index 33822d81f..000000000 --- a/docs/_site/concepts.html +++ /dev/null @@ -1,1090 +0,0 @@ - - - - - - - Slack | Bolt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - -
- - -
-

Listening to messages

- -
-

To listen to messages that your app has access to receive, you can use the message() method. This method filters out any events that aren’t of type message.

- -

It accepts an optional pattern parameter of type string or RegExp object that will filter out any messages that don’t match.

-
- -
-
-
// This will match any message that contains 👋
-app.message(':wave:', async ({ message, say}) => {
-  say(`Hello, <@${message.user}>`);
-});
-
-
-
- -
- -

Using a RegExp pattern

-
- -
-

A RegExp pattern can be used instead of a string for more granular matching.

- -

All of the results of the RegExp match will be in context.matches.

-
- -
-
-
app.message(/^(hi|hello|hey).*/, async ({ context, say }) => {
-  // RegExp matches are inside of context.matches
-  const greeting = context.matches[0];
-  
-  say(`${greeting}, how are you?`);
-});
-
-
-
- -
- - -
-
- -
-

Sending messages

- -
-

Within your listener function, say() is available whenever there is an associated conversation (for example, the conversation where the event or action which triggered the listener occurred). say() accepts a string to post simple text-based messages and JSON payloads to send more complex messages. The message payload you pass in will be sent to the associated conversation.

- -

In the case that you’d like to send a message outside of a listener or you want to do something more advanced (like handle specific errors), you can call chat.postMessage using the client attached to your Bolt instance.

-
- -
-
-
// Listens for messsages containing "knock knock" and responds with an italicized "who's there?"
-app.message('knock knock', ({ message, say }) => {
-  say(`_Who's there?_`);
-});
-
-
-
- -
- -

Sending a message with blocks

-
- -
-

say() accepts more complex message payloads to make it easy to add functionality and structure to your messages.

- -

To explore adding rich message layouts to your app, read through the guide on our API site and look through templates of common app flows in the Block Kit Builder.

-
- -
-
-
// Sends a section block with datepicker when someone reacts with a 📅 emoji
-app.event('reaction_added', ({ event, say }) => {
-  if (event.reaction === 'calendar') {
-    say({
-      blocks: [{
-          "type": "section",
-          "text": {
-            "type": "mrkdwn",
-            "text": "Pick a date for me to remind you"
-          },
-          "accessory": {
-            "type": "datepicker",
-            "action_id": "datepicker_remind",
-            "initial_date": "2019-04-28",
-            "placeholder": {
-              "type": "plain_text",
-              "text": "Select a date"
-             }
-          }
-        }]});
-  }
-});
-
-
-
-
- - -
-
- -
-

Listening to events

- -
-

You can listen to any Events API event using the event() method by subscribing to it in your app configuration. on your This allows your app to take action when something happens in Slack, like a user reacting to a message or joining a channel.

- -

The event() method requires an eventType of type string.

-
- -
-
-
const welcomeChannelId = 'C12345';
-
-// When a user joins the team, send a message in a predfined channel asking them to introduce themselves
-app.event('team_join', async ({ event, context }) => {
-  try {
-    const result = await app.client.chat.postMessage({
-      token: context.botToken,
-      channel: welcomeChannelId,
-      text: `Welcome to the team, <@${event.user.id}>! 🎉 You can introduce yourself in this channel.`
-    });
-    console.log(result);
-  }
-  catch (error) {
-    console.error(error);
-  }
-});
-
-
-
- -
- -

Filtering on message subtypes

-
- -
-

A message() listener is equivalent to event('message')

- -

You can filter on subtypes of events by using the built-in matchEventSubtype() middleware. Common message subtypes like bot_message and message_replied can be found on the message event page.

-
- -
-
-
// Matches all messages from bot users
-app.message(subtype('bot_message'), ({ message }) => {
-  console.log(`The bot user ${message.user} said ${message.text}`);
-});
-
-
-
- -
- - -
-
- -
-

Using the Web API

- -
-

You can call any Web API method using the WebClient provided to your Bolt app as app.client (given that your app has the appropriate scopes). When you call one the client’s methods, it returns a Promise containing the response from Slack.

- -

The token used to initialize Bolt can be found in the context object, which is required for most Web API methods.

-
- -
-
-
// Unix Epoch time for September 30, 2019 11:59:59 PM
-const whenSeptemberEnds = 1569887999;
-
-app.message('wake me up', async ({ message, context }) => {
-  try {
-    // Call the chat.scheduleMessage method with a token
-    const result = await app.client.chat.scheduleMessage({
-      // The token you used to initalize your app is stored in the `context` object
-      token: context.botToken,
-      channel: message.channel.id,
-      post_at: whenSeptemberEnds,
-      text: 'Summer has come and passed'
-    });
-  }
-  catch (error) {
-    console.error(error);
-  }
-});
-
-
-
- - -
-
- -
-

Listening to actions

- -
-

Your app can listen to user actions like button clicks, menu selects, and message actions using the action method.

- -

Actions can be filtered on an action_id of type string or RegExp. action_ids act as unique identifiers for interactive components on the Slack platform.

- -

You’ll notice in all action() examples, ack() is used. It is required to call the ack() function within an action listener to acknowledge the event was received from Slack. This is discussed in the acknowledging requests section.

- -
- -
-
-
// Your middleware will be called every time an interactive component with the action_id "approve_button" is triggered
-app.action('approve_button', async ({ ack, say }) => {
-  ack();
-  // Update the message to reflect the action
-});
-
-
-
- -
- -

Listening to actions using a constraint object

-
- -
-

You can use a constraints object to listen to callback_ids, block_ids, and action_ids (or any combination of them). Constraints in the object can be of type string or RegExp.

-
- -
-
-
// Your middleware will only be called when the action_id matches 'select_user' AND the block_id matches 'assign_ticket'
-app.action({ action_id: 'select_user', block_id: 'assign_ticket' },
-  async ({ action, ack, context }) => {
-    ack();
-    try {
-      const result = await app.client.reactions.add({
-        token: context.botToken,
-        name: 'white_check_mark',
-        timestamp: action.ts,
-        channel: action.channel.id
-      });
-    }
-    catch (error) {
-      console.error(error);
-    }
-});
-
-
-
- -
- - -
-
- -
-

Responding to actions

- -
-

There are two main ways to respond to actions. The first way (and the most common way) is using the say function. The say function sends a message back to the conversation where the incoming event took place.

- -

The second way to respond to actions is using respond(), which is a simple utility to use the response_url associated with an action.

-
- -
-
-
// Your middleware will be called every time an interactive component with the action_id “approve_button” is triggered
-app.action('approve_button', ({ ack, say }) => {
-  // Acknowledge action request
-  ack();
-  say('Request approved 👍');
-});
-
-
-
- -
- -

Using respond()

-
- -
-

Since respond() is a utility for calling the response_url, it behaves in the same way. You can pass a JSON object with a new message payload that will be published back to the source of the original interaction with optional properties like response_type (in_channel or ephemeral), replace_original, and delete_original.

-
- -
-
-
// Listens to actions triggered with action_id of “user_select”
-app.action('user_choice', ({ action, ack, respond }) => {
-	ack();
-	respond(`You selected <@${action.selected_user}>`);
-});
-
-
-
- -
- - -
-
- -
-

Acknowledging requests

- -
-

Actions, commands, and options requests must always be acknowledged using the ack() function. This lets Slack know that the request was received and updates the Slack user interface accordingly. Depending on the type of request, your acknowledgement may be different. For example, when responding to a dialog submission you will call ack() with validation errors if the submission contains errors, or with no parameters if the submission is valid.

- -

We recommend calling ack() right away before sending a new message or fetching information from your database since you only have 3 seconds to respond.

-
- -
-
-
// Regex to determine if this is a valid email
-let isEmail = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/
-// This uses a constraint object to listen for dialog submissions with a callback_id of ticket_submit 
-app.action({ callback_id: 'ticket_submit' }, ({ action, ack }) => {
-	// it’s a valid email, accept the submission
-  if (isEmail.test(action.submission.email)) {
-	  ack();
-  } else {
-		// if it isn’t a valid email, acknowledge with an error
-	  ack({
-		  errors: [{
-			  "name": "email_address",
-			  "error": "Sorry, this isn’t a valid email"
-      }]
-    });
-  }
-});
-
-
-
- - -
-
- -
-

Listening and responding to commands

- -
-

Your app can use the command() method to listen to incoming slash command payloads. The method requires a commandName of type string.

- -

Commands must be acknowledged with ack() to inform Slack your app has received the request.

- -

There are two ways to respond to slash command requests. The first way is to use say(), which accepts a string or JSON payload. The second is respond() which is a utility for the response_url. These are explained in more depth in the responding to actions section.

-
- -
-
-
// The echo command simply echoes on command
-app.command('/echo', async ({ command, ack, say }) => {
-  // Acknowledge command request
-  ack();
-  
-  say(`${command.text}`);
-});
-
-
-
- - -
-
- -
-

Listening and responding to options

- -
-

The option() method listens for incoming option request payloads from Slack. Similar to actions(), -an action_id or constraints object is required.

- -

While it’s recommended to use action_id for external_select menus, dialogs do not yet support Block Kit so you’ll have to -use the constraints object to filter on a callback_id.

- -

To respond to options requests, you’ll need to ack() with valid options. Both external select response examples and dialog response examples can be found on our API site.

-
- -
-
-
// Example of responding to an external_select options request
-app.options('external_action', async ({ options, ack }) => {
-  // Get information specific to a team or channel
-  const results = await db.get(options.team.id);
-  
-  if (results) {
-    let options = [];
-    // Collect information in options array to send in Slack ack response
-    for (const result in results) {
-      options.push({
-        "text": {
-          "type": "plain_text",
-          "text": result.label
-        },
-        "value": result.value
-      });
-    }
-
-    ack({
-      "options": options
-    });
-  } else {
-    ack();
-  }
-});
-
-
-
- - -
-
- -
- -
- - -
-

Handling errors

- -
-

If an error occurs in a listener, it’s recommended you handle it directly. However, there are cases where errors may occur after your listener middleware has returned (such as say(), respond(), or not calling ack()). By default, these errors will be logged to the console. To handle them yourself, you can attach a global error handler to your app using the error(fn) method.

- -

If you want more control over errors, it’s advised to use the chat.postMessage method attached to your app under the client key (instead of say() or respond()). This returns a Promise that can be caught to handle the error.

-
- -
-
-
app.error((error) => {
-	// Check the details of the error to handle cases where you should retry sending a message or stop the app
-	console.error(error);
-});
-
-
-
- - -
-
- -
-

Authorization

- -
-

Authorization is the process of deciding which Slack credentials (such as a bot token) should be available while processing a specific incoming event.

- -

Custom apps installed on a single workspace can simply use the token option at the time of App initialization. However, when your app needs to handle multiple tokens, such as cases where it will be installed on multiple workspaces or needs access to more than one user token, the authorize option should be used instead.

- -

The authorize option should be set to a function that takes an event source as its input, and should return a Promise for an object containing the authorized credentials. The source contains information about who and where the event is coming from using properties like teamId (always available), userId, conversationId, and enterpriseId.

- -

The authorized credentials should also have a few specific properties: botToken, userToken, botId (required for app to ignore messages from itself), and botUserId. You can also include any other properties you’d like to make available on the context.

- -

You should always provide either one or both of the botToken and userToken properties. One of them is necessary to make helpers like say() work. If they are both given, then botToken will take precedence.

-
- -
-
-
const app = new App({ authorize: authorizeFn, signingSecret: process.env.SLACK_SIGNING_SECRET });
-
-// NOTE: This is for demonstration purposes only.
-// All sensitive data should be stored in a secure database
-// Assuming this app only uses bot tokens, the following object represents a model for storing the credentials as the app is installed into multiple workspaces.
-
-const installations = [
-  {
-    enterpriseId: 'E1234A12AB',
-    teamId: 'T12345',
-    botToken: 'xoxb-123abc',
-    botId: 'B1251',
-    botUserId: 'U12385',
-  },
-  {
-    teamId: 'T77712',
-    botToken: 'xoxb-102anc',
-    botId: 'B5910',
-    botUserId: 'U1239',
-  },
-];
-
-const authorizeFn = async ({ teamId, enterpriseId }) => {
-  // Fetch team info from database. You could also set userToken instead.
-  const fetchAuthorizedTeam = new Promise((resolve, reject) => {
-    teamInfo[source.enterpriseId][source.teamId] !== undefined ?
-      Promise.resolve(teamInfo[source.enterpriseId][source.teamId]) :
-      Promise.reject();
-  });
-
-  const authorizedTeam = await fetchAuthorizedTeam;
-
-  return () => ({
-    botToken: authorizedTeam.botToken,
-    botId: authorizedTeam.botId,
-    botUserId: authorizedTeam.botUserId,
-  });
-}
-
-
-
- - -
-
- -
-

Conversation stores

- -
-

Bolt includes support for a store, which sets and retrieves state related to a conversation. Conversation stores have two methods:

-
    -
  • -set() modifies conversation state. set() requires a conversationId of type string, value that can be any type, and an optional expiresAt of type number. set() returns a Promise.
  • -
  • -get() fetches conversation state from the store. Get requires a conversationId of type string and returns a Promise with the conversation’s state.
  • -
- -

conversationContext() is a built-in global middleware that allows conversations to be updated by middleware. When receiving an event, middleware functions can use context.updateConversation() to set state and reading from context.conversation to retrieve it.

- -

The default (built-in) conversation store simply stores conversation state in memory. While this is sufficient for some situations, if there is more than one instance of your app running the state will not be shared among your processes so you’ll want to implement a conversation store that fetches conversation state from a database.

-
- -
-
-
const app = new App({
-  token,
-  signingSecret,
-  // It's more likely that you'd create a class for a convo store
-  convoStore: new simpleConvoStore()
-});
-
-// A simple implementation of a conversation store with a Firebase-like database
-class simpleConvoStore {
-  set(conversationId, value, expiresAt) {
-    // Returns a Promise
-    return db().ref('conversations/' + conversationId).set({ value, expiresAt });
-  }
-
-  get(conversationId) {
-    // Returns a Promise
-    return new Promise((resolve, reject) => {
-      db().ref('conversations/' + conversationId).once('value').then((result) => {
-        if (result !== undefined) {
-          if (result.expiresAt !== undefined && Date.now() > result.expiresAt) {
-            db().ref('conversations/' + conversationId).delete();
-
-            reject(new Error('Conversation expired'));
-          }
-          resolve(result.value)
-        } else {
-          // Conversation not found
-          reject(new Error('Conversation not found'));
-        }
-      });
-    });
-  }
-}
-
-
-
- - -
-
- -
-

Global middleware

- -
-

Global middleware is run for all incoming events before any listener middleware. You can add any number of global middleware to your app by utilizing app.use(fn(payload,...,next)).

- -

Both global and listener middleware must call next() to pass control of the execution chain to the next middleware, or call next(error) to pass an error back up the already-executed middleware chain.

- -

As an example, let’s say your app should only respond to users identified with a corresponding internal authentication service (an SSO provider or LDAP, for example). You may define a global middleware that looks up a user record in the authentication service and errors if the user is not found.

-
- -
-
-
// Authentication middleware that associates incoming event with user in Acme identity provider
-function authWithAcme({ payload, context, say, next }) {
-  const slackUserId = payload.user;
-  const helpChannelId = 'C12345';
-
-  // Assume we have a function that accepts a Slack user ID to find user details from Acme
-  acme.lookupBySlackId(slackUserId)
-    .then((user) => {
-      // When the lookup is successful, populate context with Acme user details
-      context.user = user;
-
-      // Pass control to the next middleware and any listener functions
-      next();
-    })
-    .catch((error) => {
-      // This user wasn't found in Acme. Send them an error and don't continue processing event
-      if (error.message === 'Not Found') {
-        app.client.chat.postEphemeral({
-          token: context.botToken,
-          channel: payload.channel,
-          user: slackUserId,
-          text: `Sorry <@${slackUserId}, you aren't registered in Acme. Please post in <#${helpChannelId} for assistance.`
-        });
-        return;
-      }
-
-      // Pass control to previous middleware (if any) or the global error handler
-      next(error);
-    });
-}
-
-
-
- - -
-
- -
-

Listener middleware

- -
-

Listener middleware is used for logic across many listener functions (but usually not all of them). They are added as arguments before the listener function in one of the built-in methods. You can add any number of listener middleware before the listener function.

- -

There’s a collection of built-in listener middleware that you can use like subtype() for filtering on a message subtype and directionMention() which filters out any message that doesn’t directly @-mention your bot.

- -

But of course, you can write your own middleware for more custom functionality. While writing your own middleware, your function must call next() to pass control to the next middleware, or calling next(error) to pass an error back up the already-executed middleware chain.

- -

As an example, let’s say your listener should only deal with messages from humans. You can write a listener middleware that excludes any bot messages.

- -
- -
-
-
// Listener middleware that filters out messages with 'bot_message' subtype
-function noBotMessages({ message, next }) {
-  if (!message.subtype || message.subtype !== 'bot_message') {
-     next();
-  }
-}
-
-// The listener only receives messages from humans
-app.message(noBotMessages, ({ message }) => console.log(
-  `(MSG) User: ${message.user}
-   Message: ${message.text}`
-));
-
-
-
- - -
-
- -
-

Adding context

- -
-

All listeners have access to a context object, which can be used to enrich events with additional information. For example, perhaps you want to add user information from a third party system or add temporary state for the next middleware in the chain.

- -

context is just an object, so you can add to it by setting it to a modified version of itself.

-
- -
-
-
async function addTimezoneContext ({ payload, context, next }) {
-  const user = await app.client.users.info({
-    token: context.botToken,
-    user: payload.user_id,
-    include_locale: true
-  });
-
-  // Add user's timezone context
-  context.tz_offset = user.tz_offset;
-}
-
-app.command('request', addTimezoneContext, async ({ command, ack, context }) => {
-  // Acknowledge command request
-  ack();
-  // Get local hour of request
-  const local_hour = (Date.UTC() + context.tz_offset).getHours();
-
-  // Request channel ID
-  const requestChannel = 'C12345';
-
-  const requestText = `:large_blue_circle: *New request from <@${command.user_id}>*: ${command.text}`;
-
-  // If request not inbetween 9AM and 5PM, send request tomorrow
-  if (local_hour > 17 || local_hour < 9) {
-    // Assume function exists to get local tomorrow 9AM from offset
-    const local_tomorrow = getLocalTomorrow(context.tz_offset);
-
-    try {
-      // Schedule message
-      const result = await app.client.chat.scheduleMessage({
-        token: context.botToken,
-        channel: requestChannel,
-        text: requestText,
-        post_at: local_tomorrow
-      });
-    }
-    catch (error) {
-      console.error(error);
-    }
-  } else {
-    try {
-      // Post now
-      const result = app.client.chat.postMessage({
-        token: context.botToken,
-        channel: requestChannel,
-        text: requestText
-      });
-    }
-    catch (error) {
-      console.error(error);
-    }
-  }
-});
-
-
-
- - -
-
- -
-

Logging

- -
-

By default, Bolt will log information from your app to the console. You can customize how much logging occurs by passing a logLevel in the constructor. The available log levels in order of most to least logs are DEBUG, INFO, WARN, and ERROR.

-
- -
-
-
// Import LogLevel from the package
-const { App, LogLevel } = require('@slack/bolt');
-
-// Log level is one of the options you can set in the constructor
-const app = new App({
-  token,
-  signingSecret,
-  logLevel: LogLevel.DEBUG,
-});
-
-
-
- -
- -

Sending log output somewhere besides the console

-
- -
-

If you want to send logs to somewhere besides the console or want more control over the logger, you can implement a logger. A custom logger must implement specific methods (known as the Logger interface):

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MethodParametersReturn type
setLevel()level: LogLevelvoid
setName()name: stringvoid
debug()...msgs: any[]void
info()...msgs: any[]void
warn()...msgs: any[]void
error()...msgs: any[]void
- -

A very simple custom logger might ignore the name and level, and write all messages to a file.

-
- -
-
-
const { App } = require('@slack/bolt');
-const { createWriteStream } = require('fs');
-const logWritable = createWriteStream('/var/my_log_file'); // Not shown: close this stream
-
-const app = new App({
-  token,
-  signingSecret,
-  // Creating a logger as a literal object. It's more likely that you'd create a class.
-  logger: {
-    debug(...msgs): { logWritable.write('debug: ' + JSON.stringify(msgs)); },
-    info(...msgs): { logWritable.write('info: ' + JSON.stringify(msgs)); },
-    warn(...msgs): { logWritable.write('warn: ' + JSON.stringify(msgs)); },
-    error(...msgs): { logWritable.write('error: ' + JSON.stringify(msgs)); },
-    setLevel(): { },
-    setName(): { },
-  },
-});
-
-
-
- -
- - -
-
- -
-

Customizing a receiver

- -
-

A receiver is responsible for handling and parsing any incoming requests from Slack, then emitting the request so the Bolt app can add context and pass the request to your app’s listeners. Receivers must conform to the Receiver interface:

- - - - - - - - - - - - - - - - - - - - - - - - - - -
MethodParametersReturn type
on() -type: string, listener: fn() -unknown
start()NonePromise
stop()NonePromise
- -

on() is called two times in the Bolt app:

-
    -
  • -Receiver.on('message', listener) should route all incoming requests that have been parsed to onIncomingEvent(). It’s called in the Bolt app as this.receiver.on('message', message => this.onIncomingEvent(message)).
  • -
  • -Receiver.on('error', listener) should route errors to the global error handler. It’s called in the Bolt app as this.receiver.on('error', error => this.onGlobalError(error)).
  • -
- -

To use a custom receiver, you’ll need to pass it into the constructor when initializing your Bolt app. Here is what a basic custom receiver might look like.

- -

For a more in-depth look at a receiver, read the source code for the built-in Express receiver

-
- -
-
-
import { EventEmitter } from 'events';
-import { createServer } from 'http';
-import express from 'express';
-
-// EventEmitter handles the on() function for us
-// https://nodejs.org/api/events.html#events_emitter_on_eventname_listener
-class simpleReceiver extends EventEmitter {
-  constructor(signingSecret, endpoints) {
-    super();
-    this.app = express();
-    this.server = createServer(this.app);
-
-    for (const endpoint of endpoints) {
-      this.app.post(endpoint, this.requestHandler.bind(this));
-    }
-  }
-
-  start(port) {
-    return new Promise((resolve, reject) => {
-      try {
-        this.server.listen(port, () => {
-          resolve(this.server);
-        });
-      } catch(error) {
-        reject(error);
-      }
-    });
-  }
-
-  stop() {
-    return new Promise((resolve, reject) => {
-      this.server.close((error) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve();
-      })
-    })
-  }
-
-  // This is a very simple implementation. Look at the ExpressReceiver source for more detail
-  requestHandler(req, res) {
-    // Assume parseBody function exists to parse incoming requests
-    const parsedReq = parseBody(req);
-    const event = {
-      body: parsedReq.body,
-      // Receivers are responsible for handling acknowledgements
-      ack: (response) => {
-        if (!response) {
-          res.send('')
-        } else {
-          res.send(response);
-        }
-      }
-    };
-    this.emit('message', event);
-  }
-}
-
-
-
- - -
-
- -
-
- -
- - \ No newline at end of file diff --git a/docs/_site/tutorial/getting-started.html b/docs/_site/tutorial/getting-started.html deleted file mode 100644 index 38bd59487..000000000 --- a/docs/_site/tutorial/getting-started.html +++ /dev/null @@ -1,496 +0,0 @@ - - - - - - - Slack | Bolt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- - -
-

Getting started

- -
-

This guide is meant to walk you through getting up and running with a Slack app using Bolt. Along the way, we’ll create a new Slack app, set up your local environment, and develop an app that listens and responds to messages from a Slack workspace.

-
- - - -
- -

Create an app

-

First thing’s first: before you start developing with Bolt, you’ll want to create a Slack app.

- -
-

💡 We recommend using a workspace where you won’t disrupt real work getting done — you can create a new one for free.

-
- -

After you fill out an app name (you can change it later) and picking a workspace to install it to, hit the Create App button and you’ll land on your app’s Basic Information page.

- -

This page contains an overview of your app in addition to important credentials you’ll need later, like the Signing Secret under the App Credentials header.

- -

Basic Information page

- -

Look around, add an app icon and description, and then let’s start configuring your app 🔩

- -
- -

Tokens and installing apps

-

Slack apps use the industry standard OAuth to manage access to Slack’s APIs. When an app is installed, you’ll receive a token that your app can use to call various API methods.

- -

There are two token types available to a Slack app: user (xoxp) tokens and bot (xoxb) tokens. User tokens allow you to call API methods on behalf of users who are a part of your workspace. By default, your app receive an xoxp token associated with the person who installs the app. Bot tokens require adding a bot user to your app, which is granted a default set of permissions.

- -

Tokens require one or more scopes, which define the actions the token can perform. Every API method has a corresponding scope that’s required for an app to call the method.

- -

You can learn more about the different token types on our API site. The type of token your app needs depends on the actions you want it to perform. For brevity, we’re going to use bot tokens for this guide.

- -

To add a bot user, click Bot Users on the left sidebar and then Add A Bot User. Give it a display name and username, then click Add Bot User.

- -

Now that you have a bot user with permission to send messages to Slack, let’s install the app to your workspace.

- -

Click Install App on the left sidebar and click the big Install App to Workspace button at the top of the page. If you’ve never installed this Slack app before, you’ll see a screen that details what permissions this app is requesting. This is what determines the scopes that are applied to your app’s OAuth token(s).

- -

Once you authorize the installation, you’ll land on the OAuth & Permissions page.

- -

OAuth Tokens

- -

You’ll see two tokens. For now, we’ll just use the xoxb bot token. (If you scroll down this page to the Scopes section, you’ll see the various scopes you can add to the xoxp token.)

- -
-

💡 Treat your token like a password and keep it safe. Your app uses it to post and retrieve information from Slack workspaces.

-
- -

Setting up your local project

-

With the initial configuration handled, it’s time to set up a new Bolt project. This is where you’ll write the code that handles the logic for your app. One important thing to note is that Slack doesn’t host your code — you do.

- -

If you don’t already have a project, let’s create a new one. Create an empty directory and initialize a new project:

- -
-
-
mkdir first-bolt-app
-cd first-bolt-app
-npm init
-
-
-
- -

You’ll be prompted with a series of questions to describe your new project (you can accept the defaults if you aren’t picky). After you’re done, you’ll have a new package.json file in your directory.

- -

Before we install the Bolt package to your new project, let’s save the bot token and signing secret that was generated when you configured your app. These should be stored as environment variables and should not be saved in version control.

- -
    -
  1. Copy your Signing Secret from the Basic Information page and then store it in a new environment variable. The following example works on Linux and MacOS; but similar commands are available on Windows.
  2. -
- -
-
-
export SLACK_SIGNING_SECRET=<your-signing-secret>
-
-
-
- -
    -
  1. Copy your bot (xoxb) token from the OAuth & Permissions page and store it in another environment variable.
  2. -
- -
-
-
export SLACK_BOT_TOKEN=xoxb-<your-bot-token>
-
-
-
- -

Now, lets create your app. Install the @slack/bolt package and save it to your package.json dependencies using the following command:

- -
-
-
npm install @slack/bolt
-
-
-
- -

Create a new file called app.js in this directory and add the following code:

- -
-
-
const { App } = require('@slack/bolt');
-
-// Initializes your app with your bot token and signing secret
-const app = new App({
-  token: process.env.SLACK_BOT_TOKEN,
-  signingSecret: process.env.SLACK_SIGNING_SECRET
-});
-
-(async () => {
-  // Start your app
-  await app.start(process.env.PORT || 3000);
-
-  console.log('⚡️ Bolt app is running!');
-})();
-
-
-
- -

Your token and signing secret are all that is required to create your first Bolt app. Save your app.js file then, back at the command line, run the following:

- -
node app.js
-
- -

Your app should let you know that it’s up and running.

- -
- -

Setting up events

-

Your app behaves similarly to people on your team — it can respond to that happen, post messages, and more. To listen to events happening in a Slack workspace (like when a message is posted or when a emoji reaction is posted to a message) you’ll use the Events API to listen for specific events.

- -

To enable events for your app, start by going back to your app configuration page (click on the app from your app management page). Click Event Subscriptions on the left sidebar. Toggle the switch labeled Enable Events.

- -

You’ll see a text input labeled Request URL. The Request URL is a public endpoint where Slack will send HTTP POST requests about the events you specify.

- -
-

⚙️We’ve collected some of the most common hosting providers Slack developers use to host their apps on our API site

-
- -

When one of the events occurs, Slack will send your app some information about the event, like the user that triggered it and the channel it occured in. Your app will process the JSON and can then respond accordingly.

- -
- -

Using a local Request URL for development

-
- -

If you’re just getting started with your app’s development, you probably don’t have a publicly accessible URL yet. Eventually, you’ll want to set that up, but for now a development proxy like ngrok will do the job. We’ve written a separate tutorial about using ngrok with Slack for local development that should help you get everything set up.

- -

Once you’ve installed a development proxy, run it to begin forwarding requests to a specific port (we’re using port 3000 for this example, but if you customized the port used to initialize your app use that port instead):

- -
-
-
ngrok http 3000
-
-
-
- -

Running ngrok

- -

The output should show a generated URL that you can use (we recommend the one that starts with https://). This URL will be the base of your request URL, in this case https://8e8ec2d7.ngrok.io.

- -
- -
- -

Now you have a public-facing URL for your app that tunnels to your local machine. The Request URL that you use in your app configuration is composed of your public-facing URL combined with the endpoint your app is listening on. By default, Bolt apps listen on the /slack/events endpoint so our full request URL would be https://8e8ec2d7.ngrok.io/slack/events.

- -

Under the Enable Events switch in the Request URL box, go ahead and paste in your URL. As long as your Bolt app is still running, your endpoint should become verified.

- -
- -

Listening and responding to a message

-

Your app is now ready for some logic. Let’s start by using the message() listener that listens for messages in channels your bot user is a member of.

- -

The following example listens to all messages that contain the word “hello” and responds with “Hey there @user!”

- -
-
-
const { App } = require('@slack/bolt');
-
-const app = new App({
-  token: process.env.SLACK_BOT_TOKEN,
-  signingSecret: process.env.SLACK_SIGNING_SECRET
-});
-
-// Listens to incoming messages that contain "hello"
-app.message('hello', ({ message, say }) => {
-  // say() sends a message to the channel where the event was triggered
-  say(`Hey there <@${message.user}>!`);
-});
-
-(async () => {
-  // Start your app
-  await app.start(process.env.PORT || 3000);
-
-  console.log('⚡️ Bolt app is running!');
-})();
-
-
-
- -

If you restart your app, you should be able to add your bot user to a channel, send any message that contains “hello”, and it will respond.

- -

This is a basic example, but it gives you a place to start customizing your app based on your end goals. Let’s try something a little more interactive by sending a button rather than plain text.

- -
- -

Sending and responding to actions

- -

To use features like buttons, select menus, datepickers, dialogs, and message actions, you’ll need to enable interactivity. Similar to events, you’ll need to specify a URL for Slack to send the action (such as user clicked a button).

- -

Back on your app configuration page, click on Interactive Components on the left side. You’ll see that there’s another Request URL box.

- -

By default, Bolt is configured to use same endpoint for interactive components that it uses for events, so use the same request URL as above (in the example, it was https://8e8ec2d7.ngrok.io/slack/events). Press the Save Changes button in the lower right hand corner, and that’s it. Your app is all set up for interactivity!

- -

Configuring a Request URL

- -

Now, let’s go back to your app’s code and add our own interactivity. This will consist of two steps:

-
    -
  • First, your app will send a message that contains a button.
  • -
  • Next, your app will set up a listener that responds to a user when they click your app’s button
  • -
- -

Below, I’ve modified the app code we wrote in the last section to send a message with a button rather than a string:

- -
-
-
const { App } = require('@slack/bolt');
-
-const app = new App({
-  token: process.env.SLACK_BOT_TOKEN,
-  signingSecret: process.env.SLACK_SIGNING_SECRET
-});
-
-// Listens to incoming messages that contain "hello"
-app.message('hello', ({ message, say }) => {
-  // say() sends a message to the channel where the event was triggered
-  say({
-    blocks: [
-	    {
-		    "type": "section",
-        "text": {
-          "type": "mrkdwn",
-          "text": `Hey there <@${message.user}>!`
-        },
-        "accessory": {
-          "type": "button",
-          "text": {
-            "type": "plain_text",
-            "text": "Click Me"
-          },
-          "action_id": "button_click"
-		    }
-	    }
-    ]
-  });
-});
-
-(async () => {
-  // Start your app
-  await app.start(process.env.PORT || 3000);
-
-  console.log('⚡️ Bolt app is running!');
-})();
-
-
-
- -

The value inside of say() is now an object that contains an array of blocks. Blocks are the building components of a Slack message and can range from text to images to datepickers. In this case, your app will respond with a section block that includes a button as an accessory.

- -

You’ll notice in the same accessory object as the button, there is an action_id. This will act as a unique identifier for the button so your app can specify what action it is responding to.

- -
-

💡 The Block Kit Builder is an simple way to prototype your interactive messages. The builder lets you (or anyone on your team) mockup messages and generates the corresponding JSON that you can paste directly in your app.

-
- -

Now, if you restart your app and say “hello” in a channel your app is in, you’ll see a message with a button. But if you click the button, nothing happens (yet!).

- -

Let’s add a handler to send a followup message when someone clicks the button:

- -
-
-
const { App } = require('@slack/bolt');
-
-const app = new App({
-  token: process.env.SLACK_BOT_TOKEN,
-  signingSecret: process.env.SLACK_SIGNING_SECRET
-});
-
-// Listens to incoming messages that contain "hello"
-app.message('hello', ({ message, say }) => {
-  // say() sends a message to the channel where the event was triggered
-  say({
-    blocks: [
-	    {
-		    "type": "section",
-        "text": {
-          "type": "mrkdwn",
-          "text": `Hey there <@{message.user}>!`
-        },
-        "accessory": {
-          "type": "button",
-          "text": {
-            "type": "plain_text",
-            "text": "Click Me"
-          },
-          "action_id": "button_click"
-		    }
-	    }
-    ]
-  });
-});
-
-app.action('button_click', ({ body, ack, say }) => {
-  // Acknowledge the action
-  ack();
-  say(`<@${body.user.id}> clicked the button`);
-});
-
-(async () => {
-  // Start your app
-  await app.start(process.env.PORT || 3000);
-
-  console.log('⚡️ Bolt app is running!');
-})();
-
-
-
- -

You can see that we used the action_id to add a listener for our button action. If you restart your app and click the button, you’ll see a new message from your app that says you clicked the button.

- -
- -

Next steps

-

You just built your first Bolt app! 🎉

- -

Now that you have a basic app up and running, you can start exploring the parts of Bolt that will make your app stand out. Here are some ideas about where to look next:

- - - -
-
- -
- - \ No newline at end of file