Our future generation platform is in closed beta. Please request to participate. During the beta, expect some rough edges, broken windows overlooking blue sky vistas, and regularly scheduled changes.

Launch workflows with Triggers

Apps can define Functions and Functions can be bundled together as Workflows and Triggers are how Workflows get called by end users. If a function performs a job and a Workflow groups functions together into logical steps, a Trigger is how you expose your Workflow for people to use.

As a Slack connoisseur yourself, you are likely already familiar with one type of trigger, the shortcut. Shortcuts are called directly by the user and include global shortcuts that can be called from anywhere and message shortcuts that include the context of the message from which the shortcut was invoked.

In addition to shortcuts, there are event triggers that can be tripped by things that happen in Slack, like reactions being added to messages or the publishing of Message Metadata.

Defining a trigger

To create a trigger, start by creating a file in the /triggers subdirectory and name it something helpful, like my_global_shortcut.ts or new_customer_metadata_event.ts.

A trigger definition then looks like this:

import { DefineTrigger, TriggerTypes } from "slack-cloud-sdk/mod.ts";
import { ReverseEchoString } from "../workflows/reverse_echo.ts";

export const ReverseEchoShortcut = DefineTrigger("reverse_echo_shortcut", {
  type: TriggerTypes.Shortcut,
  name: "reverse",
  description: "Reverses a string and echoes it in-channel",
})
  .runs(ReverseEchoString)
  .withInputs((context) => ({
    channel: context.data.channel_id,
  }));

In the above example, you can see the trigger imports the trigger definition from the SDK as well as the Workflow that the trigger will call.

The trigger then defines a shortcut that then calls the ReverseEchoString workflow. This could have just as easily been a a message shortcut, all that would be required would be to change the type to TriggerTypes.MessageShortcut.

Technically, the definition is everything you need to create a valid trigger, but you almost certainly want that trigger to run a function and pass that function some inputs. Triggers use the method chaining pattern to add functionality to the trigger definition.

At a minimum, you'll want to include the runs and withInputs methods; runs specifies which workflow should be executed when a trigger is tripped and withInputs provides a context relevant to the type of trigger that can be used to pass input parameters to the workflow.

The ReverseEchoString expects a channel input and a stringToReverse input; the channel is provided by the context of the shortcut. The stringToReverse is expected but not provided by the trigger, so the user will automatically be asked for any missing inputs via a modal window that appears after the trigger is tripped. This makes it easy to build triggers, associate the variables based on context, and then just ask the user for any missing inputs. The type system that the inputs and outputs for functions and workflows makes the automation possible.

The tables below outline the details of the trigger types and the additional methods you can add to your trigger definition.


Trigger types

Trigger Type Use case
Global shortcut Invoke a Workflow from anywhere in Slack
Message shortcut Invoke a Workflow from a particular message
Event Invoke a Workflow when a specific event happens in Slack
✨ Filterable!
Webhook Invoke a Workflow when a specific URL receives a POST request
✨ Filterable!

Configuration methods

These methods are chained to the definition of the trigger. At the very least, a trigger must specify a workflow with the .runs() method. Some triggers require specific methods to be configured. For example: Webhook and Event triggers are "attached" to a channel via the .availableToChannel() method.

availableToWorkspaceUsers

.availableToWorkspaceUsers('U1234567', 'U9876543')

By default, triggers are only available to the owner of the application, so any other specific person who should have access to a trigger must be explicitly granted access.

To provide specific workspace users access to a trigger, provide a list of their Slack user IDs to the availableToWorkspaceUsers() method.

Example usage:

.withInputs((context) => ({
  channelId: context.data.channel_id,
  userId: context.data.user_id
}))
.availableToWorkspaceUsers('U1234567','U9876543');

While you cannot set a trigger to be available to everyone in a workspace, you can set a trigger to be available to everyone in a specific channel with the availableToChannel() method.


availableToChannel

.availableToChannel('C42424242')

By default, triggers are only available to the owner of the application, so any other specific person who should have access to a trigger must be explicitly granted access.

To provide all members of a specific channel access to a trigger, provide the channel ID to the availableToChannel() method.

Example usage:

.withInputs((context) => ({
  channelId: context.data.channel_id,
  userId: context.data.user_id
}))
.availableToChannel('C8675309');

If you call .availableToChannel() more than once, only the last call will take effect.

To grant access to specific users in a workspace, use the availableToWorkspaceUsers() method.


disable

.disable()

Disable the trigger. On its own, an effective way to remove the trigger from usage but can also be overriden by the enable method within a more specific context, such as an environment.


enable

.enable()

Enable the trigger if it has been disabled. Most likely to be used within a more specific context, such as an environment.


environment

.environment('dev', (trigger) => {
    trigger.availableToChannel("C1234567").enable();
})
.environment('prod', (trigger) => {
    trigger.availableToWorkspaceUsers(['U12345678', 'U98976543']).enable();
})

Change the behavior of the trigger based on the environment where the app is deployed. Takes a string that must match the name of an environment and defines an expression with a trigger parameter that references the current trigger in order to chain additional methods for the context of the environment.

For example, if you wanted to disable a trigger by default but enable it for everyone in the dev environment and restrict the trigger to specific set of users in prod, chaining together some .environment calls would allow you to do this.


runs

All triggers can be configured to invoke a specific workflow via the .runs() method, like so:

import { SomeWorkflow } from './workflows/some_workflow.ts'; 

export const myTrigger = DefineTrigger(
  //...
)
.runs(SomeWorkflow);

withFilter

Trigger Filters allow you to define a set of conditions for a trigger which must be true in order for the trigger to trip. For example, you might want to have a trigger that only trips if a specific reaction (e.g., 👀) was added.

To add a filter to your trigger, chain the withFilter method to your trigger definition, and pass in a filter payload.

A filter payload is a JSON object that expresses the boolean logic for your filter (e.g. "Only trip if x == 1 AND y != 2"). The payload structure depends on the kind of filter you are building, which may be a single-condition filter or a multi-condition filter.

Single-condition filters have a root block with a single statement child block:

.withFilter( (context) => ({
  root: {
    statement: `${context.data.some_variable} == "some_string"`,
  },
}));

Multi-condition filters have a root block with both operator and inputs child blocks. The inputs block can contain a single statement or another operator and inputs pair:

.withFilter( (context) => ({
  root: {
    operator: "AND",
    inputs: [
      { statement: `${context.data.team_name} == "Incident Response Team"` },
      {
        operator: "OR",
        inputs: [
          { statement: `${context.data.severity} >= 5` },
          { statement: `${context.data.manager_override} == true` }
        ]
      }
    ],
  },
}));

The following operators are supported in filter payloads: AND, NOT, and OR.


withInputs

You can map contextual data from a trigger into a workflow's input parameters by chaining .withInputs() to the workflow declaration. Every trigger type has it's own context object that represents the unique data associated with it.

export const SomeTrigger = DefineTrigger(
  //...
)
.runs(SomeWorkflow)
.withInputs((context) => ({
  myInput: context.data.command_text
}));

Each context object is wrapped in a consistent trigger envelope that looks like this:

{
  type: string, // trigger-type, i.e. "shortcut"
  data: {
    ... // trigger-type specific payloads
  }
}

The structure of the data payload depends on the trigger type, and can be referenced from the context object of withInputs().


Fun facts

Here are some fun facts about triggers:

  • If you change the name of a trigger that's in multiple channels, the name will change in all of those channels.
  • If a trigger is configured for multiple channels and you remove it from one of those channels, it will remain in the other channels.
  • The maximum number of channels a trigger can be used in is 20.

Onward

Triggers are an important and foundational part of building a complete app with Workflows.

From here, you might explore creating a table to store data or dive into managing your app's environment.

Was this page helpful?