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:
- Post a message to a channel with an interactive component (button, select menu, etc).
- Listen for a user interaction with that component (using blockActionInteraction).
- Call
openView
with thetrigger_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",
},
],
},
],
});
Modal Validation
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
/** @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
A unique string. Please see the Keys and Resumability doc for more info.
The trigger ID from the original event. Must be called within 3 seconds of the original event.
The view to open.
Response
Always true; non-ok responses will halt the workflow run and throw an error.
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();