This tutorial will walk you through the creation of a Slack app that can send and receive metadata events using Slack triggers, workflows, and functions.
Features you’ll use
Use the new Slack CLI to create and deploy Slack apps. With the Slack CLI, developers can create an app and generate some boilerplate code in seconds.
This section walks through setting up your environment for next gen platform development.
We've built a command line interface to make the process of creating new apps, running them, and deploying them easier. Binaries are available for macOS and Windows.
Deno is currently a pre-requisite for using the CLI — if you do not have Deno installed, please do so first.
Additionally, the experience of writing an app will be greatly enhanced if your IDE includes support for Deno. Here's a plugin to add Deno capabilities to VS Code.
To install the Slack CLI:
slack
binary to a destination in your path (we recommend /usr/local/bin
). For Windows, copy the .exe
file into any location accessible in your path.slack version
and verifying the version matches the one you downloaded.Now you can now use the Slack CLI to authenticate and create a new app.
Before you can create an app, you must authenticate.
Run slack login
— this will display some instruction text and a string to copy and paste in any channel in the test workspace.
/slackauthticket ABC123DEF...XYZ
Copy the entire string, including the leading /
, and paste it into any channel in your Slack workspace. (You are essentially calling a special slash command that will authenticate your app with Slack).
Run slack auth info
for details on your active workspace authorization.
Now that you've authenticated, you can create a new app.
This section walks you through the structure of your app and how the different pieces tie together.
We will then define a function invoked by a Slack trigger that creates a new incident channel, and sends a message with important incident data.
Now you can create an app! cd
to a directory where you have write access and want your app to live, most likely in your home. Then, run
$ slack create [optional-project-name]
This will create a new project called with a random name like priceless-lemur-123
.
If you want to name your project something specific, include an additional parameter
$ slack create my-new-project
Once the app has finished running, you'll have a new directory that contains a skeleton app.
$ cd my-new-project
The structure of these next-generation apps aims to favor convention over configuration. Directories are named logically and the files within those directories map to specific functionality with an app. For example, the functions
directory contains a list of an app's functions, with a single file for each function an app defines.
Your new project has a directory structure that should resemble the following:
.slack/
- apps.json
- cli.json
assets/
- icon.png
functions/
- myfunction.ts
tables/
- mytable.ts
triggers/
- mytrigger.ts
workflows/
- myworkflow.ts
import_map.json
project.ts
README.md
Functions: Modular, reusable, atomic blocks of functionality that can be grouped together as workflows. A function defines inputs, performs some useful bit of work using those inputs, and responds with pre-defined outputs. You'll be able to quickly write custom functions, deploy them via the CLI, and building workflows using these functions. Check out our functions documentation to learn more.
Workflows: A workflow is a group of functions, including built-in functions provided by Slack. Check out our workflows documentation to learn more.
Triggers: Define how workflows are called, such as a message action, shortcut, or event, where it is able to be executed, and who can execute it. Check out our triggers documentation to learn more.
Tables: Define tables and store data to power your application. Check out our tables documentation to learn more.
Project.ts: Contains all of the metadata about a project, such as the name of project, description, and requested OAuth scopes. It also contains a list of defined triggers and tables.
Let's use a Slack Trigger, Workflow, and Function to learn how to send message events that include metadata.
The trigger will start a workflow that displays a modal. The modal will allow a user to enter a message and a secret that's stored as metadata. When the workflow is run, it will execute a function that sends the message with the secret metadata into a Slack channel.
Let's begin by defining a function that will send a message with metadata to the Slack channel. The metadata object has an event payload that contains our secret message and the user who created the original message.
Create a new file called functions/send_metadata_event.ts
with the following content:
import { DefineFunction, Schema } from "slack-cloud-sdk/mod.ts";
export const SEND_METADATA_EVENT_TYPE = "secret_message_event_type";
export const SendMetadataEventFunction = DefineFunction(
"send_metadata_event_function",
{
title: "Send a message with metadata",
description: "Sends a message with a event metadata into a channel",
input_parameters: {
required: [],
properties: {
message: {
type: Schema.types.string,
description: "Message to send",
},
secret: {
type: Schema.types.string,
description: "Secret message included as metadata",
},
channel_id: {
type: Schema.slack.types.channel_id,
description: "Channel to send the message",
},
user_id: {
type: Schema.slack.types.user_id,
description: "User included as metadata",
}
}
},
output_parameters: {
required: [],
properties: {
ok: {
type: Schema.types.boolean,
description: "Returns true if the message was send successfully",
}
}
},
},
async ({ inputs, client }) => {
console.log(`SendMetadataEventFunction inputs: ${JSON.stringify(inputs)}`);
const resp = await client.call("chat.postMessage", {
text: `:speech_balloon: ${inputs.message}`,
channel: inputs.channel_id,
metadata: {
event_type: SEND_METADATA_EVENT_TYPE,
event_payload: {
message: inputs.message,
secret: inputs.secret,
user_id: inputs.user_id,
},
},
});
if (!resp.ok) {
console.log(`Error sending message: ${resp.error}`);
} else {
console.log(`Successfully sent message: ${JSON.stringify(resp)}`);
}
return {
outputs: {
ok: resp.ok,
},
};
},
);
Now let's define the workflow that will invoke the function we just defined. The workflow will open a modal to get a message and secret metadata from the user. It will then execute our function that sends a message with the metadata.
Create a new file called workflows/send_metadata_event.ts
with the following content:
import { DefineWorkflow, Schema } from "slack-cloud-sdk/mod.ts";
import { SendMetadataEventFunction } from "../functions/send_metadata_event.ts";
export const SendMetadataEventWorkflow = DefineWorkflow(
"send_metadata_event_workflow",
{
title: "Send a message with metadata",
description: "Send a message with custom metadata",
input_parameters: {
required: [],
properties: {
message: {
type: Schema.types.string,
description: "Message to send",
},
secret: {
type: Schema.types.string,
description: "Secret message included as metadata",
},
channel_id: {
type: Schema.slack.types.channel_id,
description: "Channel to send the message",
},
user_id: {
type: Schema.slack.types.user_id,
description: "User who created the message",
}
}
}
}
);
SendMetadataEventWorkflow.addStep(SendMetadataEventFunction, {
message: SendMetadataEventWorkflow.inputs.message,
secret: SendMetadataEventWorkflow.inputs.secret,
channel_id: SendMetadataEventWorkflow.inputs.channel_id,
user_id: SendMetadataEventWorkflow.inputs.user_id,
});
At this point we have a function that sends metadata and a workflow that invokes the function. Now we can now create a Shortcut trigger that will run the workflow, causing a modal to open so we can provide the input parameters to the workflow.
Create a file called triggers/send_metadata_event_shortcut.ts
with the following content:
import { DefineTrigger, TriggerTypes } from "slack-cloud-sdk/mod.ts";
import { SendMetadataEventWorkflow } from "../workflows/send_metadata_event.ts";
export const SendMetadataEventShortcut = DefineTrigger(
"send_metadata_event_shortcut",
{
type: TriggerTypes.Shortcut,
name: "Send a secret message",
description: "Send a message to a channel with event metadata",
},
)
.runs(SendMetadataEventWorkflow)
.withInputs((ctx) => ({
message: "This message includes a secret",
channel_id: ctx.data.channel_id,
user_id: ctx.data.user_id,
}));
Now that we've created the trigger, we need to import and reference it in the Project
definition.
Open the file project.ts
and add the SendMetadataEventShortcut
import to the triggers
property:
import { Project } from "slack-cloud-sdk/mod.ts";
import { SendMetadataEventShortcut } from "./triggers/send_metadata_event_shortcut.ts";
// ...
Project({
// ...
triggers: [SendMetadataEventShortcut],
// ...
});
It's time to try sending a message with metadata into Slack:
slack deploy
slack run
Did you notice that you can't see the secret message "Shhh..."? This is because it's part of the metadata event. Next, we'll learn how to receive the metadata event and payload.
Let's learn how to receive a metadata event using a Slack Trigger, Workflow, and Function.
We'll start by creating a trigger that listens for our metadata event. When we receive an event, then we'll run a workflow that sends a message into Slack to reveal the event's metadata.
Let's begin by creating a workflow for receiving metadata events. This workflow will use a built-in Slack function to send a message that reveals the event's metadata.
Create a new file called workflows/receive_metadata_event.ts
with the following content:
import { DefineWorkflow, Schema } from "slack-cloud-sdk/mod.ts";
export const ReceiveMetadataEventWorkflow = DefineWorkflow(
"receive_metadata_event_workflow",
{
title: "Receives and responds to a metadata event",
description: "Receives a metadata event and sends a message in response",
input_parameters: {
required: [],
properties: {
metadata: {
type: Schema.types.object,
description: "Custom message metadata",
},
channel_id: {
type: Schema.slack.types.channel_id,
description: "Channel to send the message",
}
}
}
}
);
ReceiveMetadataEventWorkflow.addStep(Schema.slack.functions.SendMessage, {
channel_id: ReceiveMetadataEventWorkflow.inputs.channel_id,
message:
`:magic_wand: Revealing the secret message:\n> ${ReceiveMetadataEventWorkflow
.inputs.metadata.event_payload?.secret} - <@${ReceiveMetadataEventWorkflow
.inputs.metadata.event_payload?.user_id}>`,
});
Next, let's define an Event trigger that listens for our custom MessageMetadataPosted
event and runs the new workflow we just created in the previous step.
Create a file called triggers/receive_metadata_event.ts
with the following content:
import { DefineTrigger, Schema, TriggerTypes } from "slack-cloud-sdk/mod.ts";
import { ReceiveMetadataEventWorkflow } from "../workflows/receive_metadata_event.ts";
import { SEND_METADATA_EVENT_TYPE } from "../functions/send_metadata_event.ts";
// TODO - Update to be your Slack channel ID (e.g. "C02AL6F9NU9")
const CHANNEL_ID = "C0XXXXXX";
export const ReceiveMetadataEvent = DefineTrigger(
"receive_metadata_event",
{
type: TriggerTypes.Event,
event_type: Schema.slack.events.MessageMetadataPosted,
metadata_event_type: SEND_METADATA_EVENT_TYPE,
},
)
.runs(ReceiveMetadataEventWorkflow)
.withInputs((ctx) => ({
channel_id: ctx.data.channel_id,
metadata: ctx.data.metadata,
}))
.availableToChannel(CHANNEL_ID);
Next, update the CHANNEL_ID
to the channel where your app is run:
Now that we've created the trigger, we need to import and reference it in the Project
definition.
Open the file project.ts
and add the ReceiveMetadataEvent
import to the triggers
property:
import { Project } from "slack-cloud-sdk/mod.ts";
import { ReceiveMetadataEvent } from "./triggers/receive_metadata_event.ts";
import { SendMetadataEventShortcut } from "./triggers/send_metadata_event_shortcut.ts";
// ...
Project({
// ...
triggers: [ReceiveMetadataEvent, SendMetadataEventShortcut],
// ...
});
To try it all out, we'll use the Shortcut trigger that we created earlier to send a metadata event. Then our new Event trigger will listen for the same metadata event and respond with a message that reveals the metadata.
slack deploy
slack run
This is a simple example that shows how to send and receive metadata events.
We do it all within one app, but your app can also listen for metadata events from other apps. This can open a whole new world of possibility, because it allows apps to communicate with other apps.
The robots are taking over! 🤖