Actions
Open View

Open a modal view for a user after a button click or other user action. The view is a modal that blocks other user interactions with your app, and can be used to collect more complex input from users.

To be able to open a view, you must first acquire a trigger_id from a user interaction, such as a button click. See the blockActionInteraction docs for more info on triggering on user actions.

trigger_id’s are valid for only 3 seconds, so make sure not to add any delays before calling openView

Usage

There are 3 steps to opening a view:

  1. Post a message to a channel with an interactive component (button, select menu, etc).
  2. Listen for a user interaction with that component (using blockActionInteraction).
  3. Call openView with the trigger_id from the interaction.
// This is inside a Trigger.run handler, but extracted for brevity
await slack.postMessage("New Issue", {
  channelName: "github-issues",
  text: "There is a new GitHub issue in the repo",
  metadata: { githubIssueId },
  blocks: [
    {
      type: "actions",
      block_id: "new.issue", // this is the block_id that we'll use to identify the message
      elements: [
        {
          type: "button",
          action_id: "reply",
          text: {
            type: "plain_text",
            text: "Reply to Issue",
          },
          value: "reply",
        },
      ],
    },
  ],
});

You can optional provide a validation schema (using Zod) which we will use to validate the view submission and provide feedback to the user.

For example, we could make sure the replyInput above is more than 3 characters and less than 500 characters:

await slack.openView(
  "Get the reply",
  event.trigger_id,
  {
    type: "modal",
    callback_id: "reply",
    title: {
      type: "plain_text",
      text: "Reply to Issue",
    },
    blocks: [
      {
        type: "input",
        block_id: "replyInput",
        element: {
          type: "plain_text_input",
          action_id: "reply",
        },
        label: {
          type: "plain_text",
          text: "Reply",
        },
      },
    ],
    submit: {
      type: "plain_text",
      text: "Submit",
    },
  },
  {
    validationSchema: z.object({
      replyInput: z.string().min(3).max(500),
    }),
  }
);

Note that the block_id of the input is used as the key in the validation schema (not the action_id).

The above example will show an error message if the user submits a reply that is less than 3 characters or more than 500 characters, but you might want to provide an even better experience by using the built-in validation provided by BlockKit:

await slack.openView("Get the reply", event.trigger_id, {
  type: "modal",
  callback_id: "reply",
  title: {
    type: "plain_text",
    text: "Reply to Issue",
  },
  blocks: [
    {
      type: "input",
      block_id: "replyInput",
      element: {
        type: "plain_text_input",
        action_id: "reply",
      },
      label: {
        type: "plain_text",
        text: "Reply",
      },
      min_length: 3,
      max_length: 500,
    },
  ],
  submit: {
    type: "plain_text",
    text: "Submit",
  },
});

See Slack’s Block Element reference docs page for more information the properties you can use to control a user’s input.

JSX Slack

You can use the JSX Slack project to build your views in a more declarative way:

Step 1: Install and configure JSX Slack

  • npm

  • pnpm

  • yarn

npm install jsx-slack

If you aren’t already using React in your project, you will need to update your tsconfig.json file to add the compilerOptions.jsx option, and ensure to include .tsx files in your include array:

{
  "compilerOptions": {
    "jsx": "react-jsx"
  },
  "include": ["src/**/*.ts", "src/**/*.tsx"]
}

Step 2: Create your Modal

modals.tsx
/** @jsxImportSource jsx-slack */
import JSXSlack, {
  Actions,
  Input,
  Textarea,
  Section,
  Header,
  Modal,
} from "jsx-slack";

export function replyModal(title: string, callbackId: string) {
  return JSXSlack(
    <Modal title="Your reply" close="Cancel" callbackId={callbackId}>
      <Header>Issue title</Header>
      <Section>{title}</Section>
      <Header>Your reply</Header>
      <Textarea
        name="message"
        label="Message"
        placeholder="Your message"
        maxLength={500}
        id="messageInput"
      />
      <Input type="submit" value="submit" />
    </Modal>
  );
}

Step 3: Open the Modal

import { replyModal } from "./modals";

await slack.openView(
  "Get the reply",
  event.trigger_id,
  replyModal("Issue title", "reply")
);

Handling a View Submission

To get the results of a view submission, you can use the viewSubmissionInteraction event, providing the callback_id of the view you want to listen for:

new Trigger({
  id: "handle-view-submission",
  name: "Handle View Submission",
  on: slack.events.viewSubmissionInteraction({
    callbackId: "reply",
  }),
  run: async (event, ctx) => {
    // use event.view.state.values to get the values of the inputs
  },
}).listen();

For more information please see the viewSubmissionInteraction docs.

Passing custom data

There are a few ways you can pass custom data to your view submission handler when opening a view:

Using the metadata option

When calling openView, you can pass a 4th argument which is object with a metadata property. This will be passed to your view submission handler as event.view.private_metadata:

import { replyModal } from "./modals";

await slack.openView(
  "Get the reply",
  event.trigger_id,
  replyModal("Issue title", "reply"),
  {
    metadata: {
      issueId: "123",
      thread_ts: event.message.ts,
    },
  }
);

Params

keyrequired
string

A unique string. Please see the Keys and Resumability doc for more info.

trigger_idrequired
string

The trigger ID from the original event. Must be called within 3 seconds of the original event.

viewrequired
view object

The view to open.

options
object

Response

okDefault: "true"
boolean

Always true; non-ok responses will halt the workflow run and throw an error.

view
object

Example Workflow

The following example combines WhatsApp and Slack to create a workflow that allows you to receive WhatsApp messages in Slack, and use a modal to compose a reply.

/** @jsxImportSource jsx-slack */
import { Trigger } from "@trigger.dev/sdk";
import {
  events,
  sendText,
  getMediaUrl,
  MessageEventMessage,
} from "@trigger.dev/whatsapp";
import JSXSlack, {
  Actions,
  Blocks,
  Button,
  Section,
  Header,
  Context,
  Image,
  Modal,
  Input,
  Textarea,
} from "jsx-slack";
import * as slack from "@trigger.dev/slack";

const dateFormatter = new Intl.DateTimeFormat("en-US", {
  timeStyle: "short",
  dateStyle: "short",
});

// this trigger listens for WhatsApp messages and sends them to Slack
new Trigger({
  id: "whatsapp-to-slack",
  name: "WhatsApp: load messages",
  on: events.messageEvent({
    accountId: "<account id>",
  }),
  run: async (event, ctx) => {
    //this generates Slack blocks from the WhatsApp message
    const messageBody = await createMessageBody(event.message);

    await slack.postMessage("jsx-test", {
      channelName: "whatsapp-support",
      //text appears in Slack notifications on mobile/desktop
      text: "How is your progress today?",
      //import and use JSXSlack to make creating rich messages much easier
      blocks: JSXSlack(
        <Blocks>
          <Header>From: {event.message.from}</Header>
          <Context>At: {dateFormatter.format(event.message.timestamp)}</Context>
          {messageBody}
          <Actions blockId="launch-modal">
            <Button value="reply" actionId="reply">
              Reply
            </Button>
          </Actions>
        </Blocks>
      ),
      //pass the WhatsApp message to the next trigger
      metadata: {
        whatsAppMessage: event.message,
      },
    });
  },
}).listen();