Intermediate

Workflow Builder: Steps from apps

Workflow steps from apps provide an ability for Slack apps to create and process custom workflow steps. These steps from apps can be shared and used by anyone in Workflow Builder.

Use steps from apps to send data to external services, create tasks in project management systems, or update tickets.

Follow our guide to learn how to build your own steps, custom tailored to your organization’s needs.


What are steps from apps?

A workflow is a multi-step process, where some of the steps are automated by Slack. Workflows are available for end-users in various locations in Slack.

Workflow Builder offers a simple set of tools to create workflows. There's no code involved, so anyone can create workflows by creating a flow of workflow steps.

In addition to the built-in steps, Slack apps can create and share their own custom steps from apps.

Users can add these steps to their workflows in Workflow Builder. The app then handles execution of that step in a workflow.

The app will receive appropriate context from the workflow, such as user inputs from earlier steps.

This feature will only work for apps using our newest granular permission model. Learn more about how to create a new Slack app.


Getting started with steps from apps

First, you'll need a Slack app. If you don't have one, you may want to read more about how they work. If you just want to create one here and now, let's just get started.

Create a Slack app

Whether you have an existing app or a freshly minted one installed to your development workspace, you'll want to go to your app's settings for the next step.

Configure your app

There are some settings changes required for your app to create and use workflow steps.

The first thing you'll need to do, if you haven't already, is to enable interactivity in your app.

Go to the Interactivity & Shortcuts section and flip the switch to 'on'. You'll need to fill in the Request URL field with a URL capable of handling interaction payloads. You'll start receiving those payloads from use of your app's steps.

The next step involves subscribing to an event and adding a permission scope, and you can add these yourself as explained below. They will, however, also be automatically added to your app's settings when creating your first workflow step later in this guide.

You'll need to subscribe to an Events API type called workflow_step_execute. This event will notify your app when a step it created has been used. Add it under the Event Subscriptions section of your app's settings.

Subscribing to the workflow_step_execute event also requires that your app request a permission scope called workflow.steps:execute. Add it in the OAuth & Permissions section, under Bot Token Scopes.

Your app will need to be re-authenticated anywhere it is already installed after you add a new scope. Otherwise, users will not see your steps in the step library.

Please note: Organizations with admin-approved apps turned on will need to approve any re-authentication. Otherwise, only the user will be asked to go through the re-authentication flow – there will be no additional work for the administrator.

Create a workflow step

In your app's config, click on the Workflow Steps section. Select the toggle to opt-in — this will automatically add a few settings (such as permission scopes and event subscriptions) so that you can start creating workflow steps.

You'll see a Steps section, listing any steps you've already created in your app. Each app can create up to 10 steps. Click the Add Step button, you'll see a modal appear with fields to configure your app's new step:

  • Step name - a descriptive name which is shown to the Workflow Builder user.
  • Callback ID - a string that will act as a unique identifier for this particular step. It will be sent back to you with each action involving your step.

Click Save and you've created your app's first workflow step!

Your app's step will now be available to Workflow Builder users in workspaces where:

  • Your app is installed.
  • An owner or admin has enabled the steps from apps feature in the workspace or Enterprise Grid org's settings.

Prepare your app for Workflow Builder

Here is the flow that occurs when a user adds your app's step in Workflow Builder:

  1. User inserts your app's step to a workflow, your app receives a workflow_step_edit payload.
  2. App uses the trigger_id from that payload to open a configuration view modal.
  3. User completes configuration modal and submits. Your app receives a view_submission payload.
  4. App creates a workflow_step object, and makes a call to workflows.updateStep.
  5. Step is successfully added to the user's workflow.

Good news: the Bolt frameworks for Javascript, Python, and Java handle all these details for you, which makes creating a step as easy as dancing a jig.

If you're already using one of these frameworks and you've got a step running, you can skip to keeping track of the workflows the use your steps.

Otherwise, if you'd like to implement the flow yourself, let's dig in.

Receiving the workflow_step_edit payload

At this point in the flow, an interaction payload will be sent to the interactive request URL configured above.

Read our guide to handling interaction payloads to understand how to receive and parse them. You can find a reference guide to the fields included in the workflow_step_edit type payload here.

Opening configuration modal

When someone building a workflow inserts your app's step, they will need a way to pass along information entered by end-users in earlier steps. We can use what we call a configuration modal to enable this.

Each field in your configuration modal should allow the builder to define the source for all input data that your app requires. The builder will be able to insert Handlebars-like {{variables}} into any plain-text field to auto-populate output data from previous steps.

The configuration modal can also be used to configure data that doesn't come from end-users, but is defined only by the Workflow Builder user.

This type of modal can be used in much the same way as regular modals. The workflow_step_edit payload mentioned above will contain a trigger_id that is needed to open the modal.

Most existing Block Kit components can be used to define the configuration modal view object.

There are a few differences between the use of regular modals and configuration modals:

  • The type property in the modal view will be workflow_step instead of modal.
  • When the submit_disabled property is set to true the submit button will be disabled until the user has completed one or more inputs.
  • Apps cannot specify title, submit or the close properties.
  • A configuration modal view can be updated as long as it remains open, but you cannot push new views to the modal view stack.
  • Configuring users will see an option to Insert a variable below any plain-text input blocks in your configuration modal. This allows the configuring user to specify that the value for any given field should be equal to the value of something collected from an end-user in an earlier workflow step. See below for more about variables.

Saving the configuration data using Bolt

When a configuration modal is submitted, your app will be sent a view_submission interaction payload.

If you're using Bolt, you can easily acknowledge and process this payload by using the save callback in the WorkflowStep class:

Capturing configuration modal view_submission interactions
Java
JavaScript
Python
Java
import java.util.*;
import com.slack.api.model.workflow.*;
import static com.slack.api.model.workflow.WorkflowSteps.*;

static String extract(Map<String, Map<String, ViewState.Value>> stateValues, String blockId, String actionId) {
  return stateValues.get(blockId).get(actionId).getValue();
}

WorkflowStep step = WorkflowStep.builder()
  .callbackId("copy_review")
  .edit((req, ctx) -> { return ctx.ack(); })
  .save((req, ctx) -> {
    Map<String, Map<String, ViewState.Value>> stateValues = req.getPayload().getView().getState().getValues();
    Map<String, WorkflowStepInput> inputs = new HashMap<>();
    inputs.put("taskName", stepInput(i -> i.value(extract(stateValues, "task_name_input", "task_name"))));
    inputs.put("taskDescription", stepInput(i -> i.value(extract(stateValues, "task_description_input", "task_description"))));
    inputs.put("taskAuthorEmail", stepInput(i -> i.value(extract(stateValues, "task_author_input", "task_author"))));
    List<WorkflowStepOutput> outputs = asStepOutputs(
      stepOutput(o -> o.name("taskName").type("text").label("Task Name")),
      stepOutput(o -> o.name("taskDescription").type("text").label("Task Description")),
      stepOutput(o -> o.name("taskAuthorEmail").type("text").label("Task Author Email"))
    );
    ctx.update(inputs, outputs);
    return ctx.ack();
  })
  .execute((req, ctx) -> { return ctx.ack(); })
  .build();

app.step(step);
JavaScript
const ws = new WorkflowStep('add_task', {
	edit: async ({ ack, step, configure }) => { },
	save: async ({ ack, step, view, update }) => {
		await ack();

		const { values } = view.state;
		const taskName = values.task_name_input.name;
		const taskDescription = values.task_description_input.description;

		const inputs = {
			taskName: { value: taskName.value },
			taskDescription: { value: taskDescription.value }
		};

		const outputs = [
			{
				type: 'text',
				name: 'taskName',
				label: 'Task name',
			},
			{
				type: 'text',
				name: 'taskDescription',
				label: 'Task description',
			}
		];

		await update({ inputs, outputs });
	},
	execute: async ({ step, complete, fail }) => { },
});
Python
def save(ack, view, update):
    ack()

    values = view["state"]["values"]
    task_name = values["task_name_input"]["name"]
    task_description = values["task_description_input"]["description"]

    inputs = {
        "task_name": {"value": task_name["value"]},
        "task_description": {"value": task_description["value"]}
    }
    outputs = [
        {
            "type": "text",
            "name": "task_name",
            "label": "Task name",
        },
        {
            "type": "text",
            "name": "task_description",
            "label": "Task description",
        }
    ]
    update(inputs=inputs, outputs=outputs)

ws = WorkflowStep(
    callback_id="add_task",
    edit=edit,
    save=save,
    execute=execute,
)
app.step(ws)

The update function will make a call to the workflows.updateStep Web API method. This method stores the configuration of your app's step for a specified workflow.

There are a couple of arguments that the update function requires - input and output.

  • inputs is a key-value map of objects describing data required by your app's step. Input values can contain Handlebars {{variables}}, which are used to include values from the outputs of some earlier step in the workflow.
  • outputs is an array of objects that describe the data your app's step will return when completed. Subsequent steps in the workflow after your app's step will be able to use these outputs as variables.

Read the reference guides for input objects and output objects to learn their full structure.

Saving configuration data manually

If you're not using Bolt, your app can manually acknowledge and close the modal view and show errors as usual. You can’t use any other type of response_action with configuration modals.

Then use the information collected from the configuration modal to form the arguments needed to call the Web API method workflows.updateStep. This method will confirm and save the configuration for your app's step within this particular workflow.

The workflow will now be saved with your app's step nestled inside. Let's move on to what happens when someone uses that newly created workflow.

Handle usage of workflows containing your app's steps

When you switched on the Workflow Steps feature in your app, you were automatically subscribed to the workflow_step_execute event. This event will be sent to your app every time a workflow containing one of the app's steps is used.

Using Bolt, you can listen for and respond to this event by adding an execute callback to your WorkflowStep class:

Capturing workflow_step_execute events
Java
JavaScript
Python
Java
import java.util.*;
import com.slack.api.model.workflow.*;

WorkflowStep step = WorkflowStep.builder()
  .callbackId("copy_review")
  .edit((req, ctx) -> { return ctx.ack(); })
  .save((req, ctx) -> { return ctx.ack(); })
  .execute((req, ctx) -> {
    WorkflowStepExecution wfStep = req.getPayload().getEvent().getWorkflowStep();
    Map<String, Object> outputs = new HashMap<>();
    outputs.put("taskName", wfStep.getInputs().get("taskName").getValue());
    outputs.put("taskDescription", wfStep.getInputs().get("taskDescription").getValue());
    outputs.put("taskAuthorEmail", wfStep.getInputs().get("taskAuthorEmail").getValue());
    try {
      ctx.complete(outputs);
    } catch (Exception e) {
      Map<String, Object> error = new HashMap<>();
      error.put("message", "Something wrong!");
      ctx.fail(error);
    }
    return ctx.ack();
  })
  .build();

app.step(step);
JavaScript
const ws = new WorkflowStep('add_task', {
	edit: async ({ ack, step, configure }) => { },
	save: async ({ ack, step, update }) => { },
	execute: async ({ step, complete, fail }) => {
		const { inputs } = step;

		const outputs = {
			taskName: inputs.taskName.value,
			taskDescription: inputs.taskDescription.value,
		};

		// if everything was successful
		await complete({ outputs });

		// if something went wrong
		// fail({ error: { message: "Just testing step failure!" } });
	},
});
Python
def execute(step, complete, fail):
    inputs = step["inputs"]
    # if everything was successful
    outputs = {
        "task_name": inputs["task_name"]["value"],
        "task_description": inputs["task_description"]["value"],
    }
    complete(outputs=outputs)

    # if something went wrong
    error = {"message": "Just testing step failure!"}
    fail(error=error)

ws = WorkflowStep(
    callback_id="add_task",
    edit=edit,
    save=save,
    execute=execute,
)
app.step(ws)

The event payload will include inputs that contain any values that your step requires. These inputs come from earlier in the workflow, or from something entered in the configuration modal when the workflow step was being inserted.

You can process information from the payload or use any other relevant data to complete the step. Your app can make API calls, publish to a database, switch a lightbulb on, refresh a user's App Home, or do anything else that is relevant to the step.

That said, within the execute callback your app must either call complete() to indicate that step execution was successful, or fail() to indicate that step execution failed.

The complete function expects an outputs object, the shape of which is dictated by the outputs array sent earlier in the WorkflowStep.save callback. Use the name from each object in that array as keys, mapped to the calculated value for each output.

Handling workflow execution manually

If you're not using Bolt, read our guide to the Events API. This guide explains how to receive and handle event notifications, which includes workflow_step_execute.

Non-Bolt apps also need to manually indicate the outcome of the step execution. Make a call to the Web API method workflows.stepCompleted to indicate a successful outcome. A call to workflows.stepFailed should come after a failed attempt to complete the step.

You can check the Activity tab in Workflow Builder to inspect every run of your workflow. Any error messages for failed runs will appear there.

Well done - you've now stepped through all the steps needed to step up your workflow step game!


Handling edits of workflow steps from apps

Once your step has been added to a workflow, configuring users will have an option to customize your step through an Edit button in Workflow Builder.

Clicking that Edit button dispatches another workflow_step_edit payload to your interactive request URL, and the user’s previously configured inputs.


Step tracking

Count your steps by subscribing to four new events—workflow_published, workflow_unpublished, workflow_deleted, and workflow_step_deleted.

These events notify your app when another workflow including a step powered by your app has been published, unpublished, or deleted—and when one of the steps powered by your app is removed from a workflow.

Use these events to keep track of which workflows make use of your steps.


Known issues

  • There is a minor, visual issue with validation errors under input blocks.

Coming soon

  • A step library for builders to browse in and find steps to add to their workflows.
  • Previewing of steps in Block Kit Builder.
  • Improved messaging to builders where an app step they've used in a workspace has stopped working. This can happen as a result of an app uninstall, step removal, or an import of a workflow into a workspace without the required app installed.

Was this page helpful?