The new Slack platform and the features described below are in beta and under active development.

Modal View Interactivity

Modals are equivalent to alert, pop-up, or dialog boxes, and consist of one or more views.

Modals capture and maintain focus within Slack, allowing you to request and collect data from the user and display data to the user: a truly interactive experience!

This page will guide you through the waters of adding modal interactivity to your app. Ahoy, let's get started!

Adding interactivity in a function definition

The first step in getting your app to set up for modal interactivity is to add the interactivity property to an existing function definition as an input parameter with the type Schema.slack.types.interactivity. For example, see how we did this in the function definition below:

//diary_function.ts
import { DefineFunction, Schema } from "deno-slack-sdk/mod.ts";

export const TreasureMapFunction = DefineFunction({
  callback_id: "treasure_map_function",
  title: "Treasure map function",
  description: "Search for treasure",
  source_file: "functions/treasure_map_function.ts",
  input_parameters: {
    properties: {
      interactivity: { // This tells Slack that your function will create interactive elements
        type: Schema.slack.types.interactivity,
      },
      channel_id: {
        type: Schema.slack.types.channel_id,
      },
      mapLocation: {
        type: Schema.types.string,
        description: "The starting map location",
      },
    },
    required: ["interactivity", "mapLocation"],
  },
  output_parameters: {
    properties: {
      newMapLocation: {
        type: Schema.types.string,
        description: "The destination map location",
      },
    },
    required: ["newMapLocation"],
  },
});

Next, we're ready to define how your app will open your modal -- but first, if you'd like to become more familiar with how modals flow behind-the-scenes, be sure to check out Using modals in Slack apps.

Opening a modal

The views that make up your modal utilize the views.open and views.push API methods to control when a modal is being triggered by the user. Once you have added the interactivity input parameter, your function implementation can leverage the interactivity pointer provided by the interactivity input that serves as a unique identifier for the interactivity event. This is accessed within your function using inputs.interactivity.interactivity_pointer.

Read on to learn two methods of opening a modal view: from a function, or using a Block Kit action.

Opening a modal view from a function

To open a modal view from within a function, use the views.open API call within a function implementation as in the following example:

export default SlackFunction(TreasureMapFunction, async ({ inputs, client }) => {
  console.log('X marks the spot!');

  await client.views.open({
    interactivity_pointer: inputs.interactivity.interactivity_pointer,
    view: {
      "type": "modal", // Specify the type as modal
      "title": {
        "type": "plain_text",
        "text": "There be treasure",
      },
      "blocks": [{
        "type": "input",
        "block_id": "section1",
        "element": {
          "type": "plain_text_input",
          "action_id": "map_input",
          "placeholder": {
            "type": "plain_text",
            "text": "Where be treasure?",
          },
        },
        "label": {
          "type": "plain_text",
          "text": "Map location",
        },
        "hint": {
          "type": "plain_text",
          "text": "A little to the left.",
        },
      }],
      "submit": {
        "type": "plain_text",
        "text": "Dig",
      },
      "callback_id": "view_treasure_map",
      "notify_on_close": true,
    },
  });

  return {
    completed: false,
  };
});

Some important considerations:

  1. Take note of the callback_id. We will use it to define view handlers that react to view open/closed events later.
  2. Set notify_on_close to true in order to trigger a view_closed event.
  3. Set the return to completed: false. This should be set to true later in your action handler.

The above are important to ensure your modal isn't left floating in the vast ocean of suspended modals.

Opening a modal view using Block Kit interactive components

Similar to opening a modal view from within a function, you can also open a modal view using a Block Kit Action Handler. Below is an example of what your code might look like:

export default SlackFunction(TreasureMapFunction, async ({ inputs, client }) => {
  // ... TreasureMapFunction implementation

}).addBlockActionsHandler(['open_treasure_map'],
  async ({ action, body, client }) => {
    await client.views.open({
      interactivity_pointer: body.interactivity.interactivity_pointer,
      view: view, // The view we defined earlier in TreasureMapFunction
    });
  }
);

Handling view events

Next, we will look at how to handle modal view events sent to your app. With your defined modal view equipped with a callback_id, you can implement a handler to respond to interactions with your modal view.

With our TreasureMap function, we can "chain" additional calls to either the addViewSubmissionHandler or addViewClosedHandler methods from our top-level function. These methods allow you to register specific function handlers to respond to either view submission or view closed events, respectively.

Here is an example code snippet for each method:

// ...
.addViewSubmissionHandler(
    // The first argument is used to match the view's `callback_id`; the second is the handler function itself
    "view_treasure_map",
    async ({ view, body }) => {
      console.log('Incoming view submission handler', body);
    }
)
//...
//...
.addViewClosedHandler(
    "view_treasure_map",
    async({ view, body }) => {
      console.log('Incoming view closed handler', body);
    }
);
// ...

Remember to add a call to the client.functions.completeSuccess endpoint to explicitly mark the function as complete. You can do that like this:

await client.functions.completeSuccess({
  function_execution_id: body.function_data.execution_id,
  outputs,
});

Or, if the function execution was not successful and you'd like to raise an error, like this:

await client.functions.completeError({
  function_execution_id: body.function_data.execution_id,
  error: "Error completing function",
});

Note that both functions.completeSuccess and functions.completeError are private methods.

Updating and pushing a modal view

In order to update a view or to push a new view, your view event handlers will use either of the views.update or views.push APIs or return an object with a special response_action property.

If you haven't already, check out Using modals in Slack apps for details about how the view stack works. It may also be helpful to become familiar with the ways you can update a view, push a new view, and close a view.

In the examples below, the addViewSubmissionHandler method registers a handler to push a new view on to the view stack. The first code snippet shows how to use the API to push a new view:

// ...
.addViewSubmissionHandler(
  "view_treasure_map",
  async ({ inputs, client, body }) => {
    await client.views.push({
      interactivity_pointer: inputs.interactivity.interactivity_pointer,
      view: view, // The view we defined earlier in TreasureMapFunction
    });
  })
// ...

The second code snippet shows how to use response_action to do the same thing. Both result in identical behavior!

// ...
.addViewSubmissionHandler(
  "view_treasure_map", 
  async () => {
    return {
      response_action: "push",
      view: view, // The view we defined earlier in TreasureMapFunction
    };
  })
// ...

Data validation

Once you have opened a modal and handled your modal views, you may decide that you'd like to display any potential data validation error messages to your users. To do that, you can apply functionality from the view-related APIs you know and love in the next-gen platform.

As long as your submission handler returns an error object defined on this page, the error messages you include in that object will be displayed right next to the relevant form fields based on their field IDs.

Sailing onward

Yarr! You now have some shiny new views weaved into your app and are on a course to providing a keen user experience. Keep reading and learn how to trigger your workflows and to include even more rich Block Kit components.

Have 2 minutes to provide some feedback?

We'd love to hear about your experience with the new Slack platform. Please complete our short survey so we can use your feedback to improve.