Workflows

Workflows are the combination of functions, executed in order. Remember:

  1. Both Slack functions and custom functions define the actions of your app.
  2. Workflows are a combination of functions, executed in order. (⬅️ you are here)
  3. Triggers execute workflows.

Depending on your use case, you'll want to acquaint yourself with either Slack functions, custom functions, or both. Then continue here to learn how to implement them in a workflow.

We'll walk through defining a workflow, adding input and output parameters, adding both a Slack function and a custom function to the workflow, and declaring the workflow in your manifest.

Defining workflows

Workflows are defined in their own files within your app's /workflows directory and declared in your app's manifest. Listing workflows in your manifest tells the CLI that they are implemented in your app β€” more on that later.

Before defining your workflow, import DefineWorkflow at the top of your workflow file:

// say_hello_workflow.ts
import { DefineWorkflow } from "deno-slack-sdk/mod.ts";

Then, create a workflow definition. This is where you set, at a minimum, the workflow's title and its unique callback ID:

// say_hello_workflow.ts
export const SayHelloWorkflow = DefineWorkflow({
  callback_id: "say_hello_workflow",
  title: "Say Hello",
});
Definition properties Description
callback_id A unique string that identifies this particular component of your app.
title The display name of the workflow that shows up in slugs, unfurl cards, and certain end-user modals.
description (Optional) A string description of this workflow.
input_parameters (Optional) See Defining input parameters.

In the next section, we'll look at input_parameters in more detail.

Defining input parameters

Workflows can pass information into both functions and other workflows that are part of its workflow steps. To do this, we define what information we want to bring in to the workflow via its input_parameters property.

A workflow's input_parameters property has two sub-properties:

  • required, which is how you can ensure that a workflow only executes if specific input parameters are provided.
  • properties, where you can list the specific parameters that your workflow accounts for. Any built-in type or custom type can be used.

Input parameters are listed in the properties sub-property. Each input parameter must include a type and a description, and can optionally include a default value.

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

// Workflow definition
export const SomeWorkflow = DefineWorkflow({
  callback_id: "some_workflow",
  title: "Some Workflow",
  input_parameters: {
    required: [],
    properties: {
      exampleString: {
        type: Schema.types.string,
        description: "Here's an example string.",
      },
      exampleBoolean: {
        type: Schema.types.boolean,
        description: "An example boolean.",
        default: true,
      },
      exampleInteger: {
        type: Schema.types.integer,
        description: "An example integer.",
      },
      exampleChannelId: {
        type: Schema.slack.types.channel_id,
        description: "Example channel ID.",
      },
      exampleUserId: {
        type: Schema.slack.types.user_id,
        description: "Example user ID.",
      },
      exampleUsergroupId: {
        type: Schema.slack.types.usergroup_id,
        description: "Example usergroup ID.",
      },
    },
  },
});

Denote which properties are required by listing their names as strings in the required property of input_parameters. For example, here's how we can indicate that a parameter named exampleUserId is required:

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

// Workflow definition
export const SomeWorkflow = DefineWorkflow({
  callback_id: "some_workflow",
  title: "Some Workflow",
  input_parameters: {
    required: ["exampleUserId"],
    properties: {
      exampleUserId: {
        type: Schema.slack.types.user_id,
        description: "Example user ID.",
      },
    },
  },
});

If a workflow is invoked and the required input parameters are not provided, the workflow will not execute.

An important distinction: input_parameters are used when defining a workflow, whereas retrieving values will use inputs. inputs is also used when implementing the logic of a custom function.

Once you've defined your workflow, you can then add functionality by calling Slack functions and custom functions. This is done with the addStep method, which takes two arguments:

  • the function you want to call
  • the inputs (if any) you want to pass to that function.

We'll see examples of how to call both types of functions in the following section.


Adding functions to workflows

Import Schema reference

The first step to adding a function to a workflow is to import Schema from the Slack SDK.

// /workflows/greeting_workflow.ts

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

Call a function with addStep

Slack functions

Slack functions are essentially Slack-native actions, like creating a channel or sending a message.

To use a Slack function, like SendMessage, let's look at an example from the Deno Hello World sample app.

After defining the workflow, call the Slack function with your workflow's addStep method:

const GreetingWorkflow = DefineWorkflow({
  callback_id: "greeting_workflow",
  title: "Send a greeting",
  description: "Send a greeting to channel",
  input_parameters: {
    properties: {
      interactivity: {
        type: Schema.slack.types.interactivity,
      },
      channel: {
        type: Schema.slack.types.channel_id,
      },
    },
    required: ["interactivity"],
  },
});

const inputForm = GreetingWorkflow.addStep(
  Schema.slack.functions.OpenForm,
  {
    title: "Send a greeting",
    interactivity: GreetingWorkflow.inputs.interactivity,
    submit_label: "Send greeting",
    fields: {
      elements: [{
        name: "recipient",
        title: "Recipient",
        type: Schema.slack.types.user_id,
      }, {
        name: "channel",
        title: "Channel to send message to",
        type: Schema.slack.types.channel_id,
        default: GreetingWorkflow.inputs.channel,
      }, {
        name: "message",
        title: "Message to recipient",
        type: Schema.types.string,
        long: true,
      }],
      required: ["recipient", "channel", "message"],
    },
  },
);

//...call GreetingFunctionDefinition in greetingFunctionStep

// Example: taking the string output from the greetingFunctionStep function and passing it to SendMessage
GreetingWorkflow.addStep(Schema.slack.functions.SendMessage, {
  channel_id: inputForm.outputs.fields.channel,
  message: greetingFunctionStep.outputs.greeting,
});

Workflows can be configured to run without any user input, or they can wait for input via form before continuing.

Using OpenForm in a workflow

The only Slack function that has an additional requirement is OpenForm. When creating a workflow that will have a step to open a form, your workflow needs to:

  • include the interactivity input parameter
  • have the call to OpenForm be its first step or ensure the preceding step is interactive. An interactive step will generate a fresh pointer to use for opening the form; for example, use the interactive button that can be added with the SendMessage Slack function immediately before opening the form.

Here's an example of a basic workflow definition using interactivity:

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

export const SayHelloWorkflow = DefineWorkflow({
  callback_id: "say_hello_workflow",
  title: "Say Hello to a user",
  input_parameters: {
    properties: { interactivity: { type: Schema.slack.types.interactivity } },
    required: ["interactivity"],
  },
});

✨ Visit the forms section for more details and code examples of using OpenForm in your app.

Custom functions

Custom functions are reusuable building blocks of automation of your own design.

To use a custom function that you have already defined:

  1. Import the function in your manifest, where you define the workflow:
import { SomeFunction } from "../functions/some_function.ts";
  1. Call your function, storing its output in a variable. Here you may also pass input parameters from the workflow into the function itself:
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
import { SomeFunction } from "../functions/some_function.ts";

export const SomeWorkflow = DefineWorkflow({
  callback_id: "some_workflow",
  title: "Some Workflow",
  input_parameters: {
    properties: {
      someString: {
        type: Schema.types.string,
        description: "Some string",
      },
      channelId: {
        type: Schema.slack.types.channel_id,
        description: "Target channel",
        default: "C1234567",
      },
    },
    required: [],
  },
});

const myFunctionResult = SomeWorkflow.addStep(SomeFunction, {
  // ... Pass along workflow inputs via SomeWorkflow.inputs
  // ... For example, SomeWorkflow.inputs.someString
});
  1. Use your function in follow-on steps. For example:
// Example: taking the string output from a function and passing it to SendMessage
SomeWorkflow.addStep(Schema.slack.functions.SendMessage, {
  channel_id: SomeWorkflow.inputs.channelId,
  message: SomeFunction.outputs.exampleOutput, // This comes from your function definition
});

Once you've added all steps and functions to your workflow, there's one final stop to having a fully functioning workflow β€” adding it to the app manifest.


Adding the workflow to the manifest

The final step of using a workflow is adding it to your manfiest. Declare your workflow in your app's manifest definition of your manifest file like this:

// manifest.ts
import { Manifest } from "deno-slack-sdk/mod.ts";
import { SayHelloWorkflow } from "./workflows/say_hello_workflow.ts";

export default Manifest({
  name: "sayhello",
  description: "A deno app with an example workflow",
  icon: "assets/icon.png",
  workflows: [SayHelloWorkflow], // Add your workflow here
  botScopes: ["commands", "chat:write", "chat:write.public"],
});

Onward

➑️ To keep building your app, head to the triggers section to learn how to create a trigger that invokes a defined workflow.

You can also learn about creating a datastore to store and retrieve information, or building custom types for your data.

Have 2 minutes to provide some feedback?

We'd love to hear about your experience building modular Slack apps. Please complete our short survey so we can use your feedback to improve.