Forms

Build data collection into your automation with forms, where information collected from a user can be mapped to the inputs of workflow steps.

If you're brand new to using forms, keep reading! Familiarizing yourself with functions and workflows will also help you ease into adding interactivity into your workflow.

If you need to update an already-created form, jump down to the schema of form field elements.

Collecting input in a workflow

To add a form to your app, we'll use the OpenForm Slack function inside your workflow.

1. Add interactivity to your workflow

First let's take a look at the "Send a Greeting" workflow from the Hello World sample app.

Making your app interactive is the key to collecting user data. To accomplish this, an interactivity input parameter must be included as a property in your workflow definition as in the following code snippet:

// greeting_workflow.ts

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

const GreetingWorkflow = DefineWorkflow({
  callback_id: "greeting_workflow",
  title: "Send a greeting",
  // Linking is supported in the form description (but not the element description).
  description: "Send a greeting to channel. Practice with the <https://api.slack.com/tutorials/tracks/hello-world|Hello World> tutorial.",
  input_parameters: {
    properties: {
      interactivity: { // Add this parameter
        type: Schema.slack.types.interactivity,
      },
      channel: {
        type: Schema.slack.types.channel_id,
      },
    },
    required: ["interactivity"],
  },
});

Learn more about the interactivity built-in type.

2. Add a form to your workflow

Now that you've added the interactivity property into your workflow, it's time to add the OpenForm Slack function to a step in your workflow.

As a refresher, a workflow is a multi-step process in which some of the steps are automated.

For example, say you need to collect some information from a user, send it to your system, then update a Slack channel with a link to a summary. Each task can be configured as a step in your workflow, allowing for user interactivity data to be passed to each step sequentially until the process is complete.

While some of the functions you add to your workflow will be custom functions, a variety of Slack functions that cover some of the most common tasks executed on our platform are also available. The OpenForm Slack function allows for the collection of user input.

Add the OpenForm Slack function as a step in your workflow:

// greeting_workflow.ts

const inputForm = GreetingWorkflow.addStep(
  Schema.slack.functions.OpenForm, // Call the OpenForm Slack function
  {
    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"],
    },
  },
);

The input you collected from the user can be passed to subsequent steps in a workflow that send messages (as in the example below), create channels, pin messages in channels, and so on:

// greeting_workflow.ts

const greetingFunctionStep = GreetingWorkflow.addStep(
  GreetingFunctionDefinition,
  {
    recipient: inputForm.outputs.fields.recipient,
    message: inputForm.outputs.fields.message,
  },
);

User inputs can also be passed to functions via steps in your workflow:

// greeting_workflow.ts

GreetingWorkflow.addStep(Schema.slack.functions.SendMessage, {
  channel_id: inputForm.outputs.fields.channel,
  message: greetingFunctionStep.outputs.greeting,
});

export default GreetingWorkflow;

Take note of the title, description, and submit_label fields. It is important be descriptive with these fields, as these are the first things the user will see once the workflow is triggered and your form is displayed to them:

form-metadata

The fields of your form are made up of different types of elements that follow a certain schema. The type of each element determines the input type and includes types such as string, boolean, timestamp, channel_id, and user_id.

Forms have two output parameters:

  • user_id: User ID of the person who submitted the form
  • fields: The same field names in the inputs, which are returned as outputs with the values entered by the user

It is important to validate the inputs you receive from the user: first, that the user is authorized to pass the input, and second, that the user is passing a value you expect to receive and nothing more.

The example above included an extra step: sending the user's message to a specific channel the user specified. Output parameters store user interactivity data, and can be used to pass that data to follow-on steps in the workflow. By using the output parameter fields and selecting the desired output element by name (i.e. recipient and message), the user's input data was passed into the second step of the workflow.

When using the OpenForm Slack function, either add it as the first step in your workflow or ensure the preceding step is interactive, as an interactive step will generate a fresh pointer to use for opening the form. For example, for a later step in your workflow, use the interactive button that can be added with the Send a message Slack function immediately before opening the form.

3. Add your workflow to your manifest

With a workflow defined and steps outlined, it's time to make this an official part of your app! Let's add the workflow definition to your manifest as in the following example:

// manifest.ts

import { Manifest } from "deno-slack-sdk/mod.ts";
import GreetingWorkflow from "./workflows/greeting_workflow.ts"; // Import your workflow

export default Manifest({
  name: "deno-hello-world",
  description:
    "A sample that demonstrates using a function, workflow and trigger to send a greeting",
  icon: "assets/default_new_app_icon.png",
  workflows: [GreetingWorkflow], // List your workflow here
  outgoingDomains: [],
  botScopes: ["commands", "chat:write", "chat:write.public"],
});

4. Add a trigger to kick off your workflow

Let's add some momentum to your workflow and create a link trigger.

In the trigger definition, add interactivity as an input value. This value holds context about the user interactivity that triggered this trigger, and passes it along to your workflow.

In a separate file, define your trigger in the following way:

// greeting_trigger.ts

import { Trigger } from "deno-slack-api/types.ts";
import GreetingWorkflow from "../workflows/greeting_workflow.ts";

const greetingTrigger: Trigger<typeof GreetingWorkflow.definition> = {
  type: "shortcut",
  name: "Send a greeting",
  description: "Send greeting to channel",
  workflow: "#/workflows/greeting_workflow",
  inputs: {
    interactivity: {
      value: "{{data.interactivity}}",
    },
    channel: {
      value: "{{data.channel_id}}",
    },
  },
};

export default greetingTrigger;

Run the following CLI command to create the link trigger:

$ slack trigger create --trigger-def triggers/greeting_trigger.ts

...

⚡ Trigger created
   Trigger ID:   Ft024ABCD12X
   Trigger Type: shortcut
   Trigger Name: Send a greeting
   URL: https://slack.com/shortcuts/Ft024ABCD12X/c001a02b13c42de35f47b55a89aad33c

Viola! You now have a shortcut URL to share in a channel or save as a bookmark, which allows you to kick off your workflow and open your form.

To learn more about triggering workflows, head to the triggers overview page.

⤵️ To learn about the different field elements of a form, read on!

Form field element schema

Form elements have several properties you can customize depending on the element type:

Property Type Description
name String The internal name of the element
title String Title of the form shown to the user. Maximum length is 25 characters
type Schema.slack.types.* The type of form element to display
description String (optional) Description of the form shown to the user
default Same type as type (optional) Default value for this field

For example, here is a code snippet of a form from the Virtual Running Buddies sample app, that collects a channel_id, a user_id, a number, and a date:

// log_run_workflow.ts

const inputForm = LogRunWorkflow.addStep(
  Schema.slack.functions.OpenForm,
  {
    title: "Log your run",
    interactivity: LogRunWorkflow.inputs.interactivity,
    submit_label: "Submit run",
    fields: {
      elements: [{
        name: "runner",
        title: "Runner",
        type: Schema.slack.types.user_id,
        default: LogRunWorkflow.inputs.user_id,
      }, {
        name: "distance",
        title: "Distance (in miles)",
        type: Schema.types.number,
        minimum: 0,
      }, {
        name: "rundate",
        title: "Run date",
        type: Schema.slack.types.date,
        default: new Date().toISOString.split("T")[0], // YYYY-MM-DD
      }, {
        name: "channel",
        title: "Channel to send entry to",
        type: Schema.slack.types.channel_id,
        default: LogRunWorkflow.inputs.channel,
      }],
      required: ["channel", "runner", "distance", "rundate"],
    },
  },
);

Form field element type parameters

The following parameters are available for each type when defining your form.

Note the distinction that some element types are prefixed with Schema.types, while some are prefixed with Schema.slack.types.

Type Parameters Notes
Schema.types.string title, description, default, minLength, maxLength, format, enum, choices, long, type If the long parameter is provided and set to true, it will render as a multi-line text box. Otherwise, it renders as a single-line text input field. In addition, basic input validation can be done by setting format to either email or url
Schema.types.boolean title, description, default, type A boolean rendered as a radio button in the form
Schema.types.integer title, description, default, enum, choices, type, minimum, maximum A whole number, such as -1, 0, or 31415926535
Schema.types.number title, description, default, enum, choices, type, minimum, maximum A number that allows decimal points, such as 13557523.0005
Schema.types.array title, description, default, type, items, maxItems The items parameter is an object itself, which must have a type sub-property defined. It can further accept multiple different kinds of sub-properties based on the type chosen. Schema.types.string, Schema.slack.types.channel_id, and Schema.slack.types.user_id are supported; see each linked type for more details on each.
Schema.slack.types.date title, description, default, enum, choices, type A string containing a date, displayed in YYYY-MM-DD format
Schema.slack.types.timestamp title, description, default, enum, choices, type A Unix timestamp in seconds, rendered as a date picker
Schema.slack.types.user_id title, description, default, enum, choices, type A user picker
Schema.slack.types.channel_id title, description, default, enum, choices, type A channel picker
Schema.slack.types.rich_text title, description, default, type A way to nicely format messages in your app. Note that this type cannot be converted to other message types, such as a string

For each parameter listed above, type is required. For arrays, items is also required. The following is an example array containing Schema.types.string types:

{
  name: "departments",
  title: "Your department",
  type: Schema.types.array,
  description: "You belong here.",
  items: {
    type: Schema.types.string,
    enum: ["marketing", "design", "sales", "engineering"],
  },
  default: ["sales", "engineering"],
}

The enum and choices parameters should be provided together. They are used to create a drop-down that allows users to select an item from a list. For example:

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

export const NewMemberIntake = DefineWorkflow({
  callback_id: "new_member_intake",
  title: "New member intake",
  input_parameters: {
    properties: { interactivity: { type: Schema.slack.types.interactivity } },
    required: ["interactivity"],
  },
});

const teammate = NewMemberIntake.addStep(
  Schema.slack.functions.OpenForm,
  {
    title: "Welcome to the team!",
    interactivity: NewMemberIntake.inputs.interactivity,
    submit_label: "Join",
    description: "There's a place for you here.",
    fields: {
      elements: [
        {
          name: "department",
          title: "What department are you in?",
          type: Schema.types.string,
          enum: ["marketing", "design", "sales", "engineering"],
          choices: [
            {
              value: "marketing",
              title: "Marketing",
              description: "Share the excitement",
            },
            {
              value: "design",
              title: "Design",
              description: "Create wonderful experiences",
            },
            {
              value: "sales",
              title: "Sales",
              description: "Solve problems with customers",
            },
            {
              value: "engineering",
              title: "Engineering",
              description: "Build systems for solutions",
            },
          ],
        },
      ],
      required: ["department"],
    },
  },
);

To learn more about workflows, check out the workflows page.

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.