These docs describe an outmoded approach to collecting input. Learn how to collect more customized data and display dynamic information with our new documentation and our guide on upgrading dialogs to modals.

Interacting with users through dialogs

Dialogs are designed to help you gather multi-part input from end-users in a structured way; think, for example, of a helpdesk ticket that contains a freeform description of an issue, but also requires some category labels for triage.

They're best used for quick, contextual work: brief tasks that get some benefit from being completed in the context of a channel or conversation.

Users trigger your app's dialogs by clicking on buttons, selecting from menus, or invoking slash commands. Dialogs contain a variety of guided input types.

Anatomy of a dialog

Dialogs appear as a modal window inside Slack. Within that modal, you can add one or many input fields in the form of single line text inputs, text areas, and dropdowns.

Anatomy of a dialog

The dialog experience focuses squarely on the task at hand. User input is validated before being sent back to your app.

Implementing dialogs in your app

Dialogs are part of the interactive framework already powering message menus and buttons.

While many aspects of dialog development are similar, the JSON used to compose a form differs significantly from interactive message components.

To continue interacting with a user after form submission, your app will need to create new messages or utilize the response_url associated with the interaction that originally spawned the dialog.

Apps can invoke dialogs when users interact with slash commands, message buttons, or message menus. Each interaction will include a trigger_id, a kind of short-lived pointer to interaction's who, what, where, and when. Form submissions deliver to the Request URL associated with your Slack app.

Dialogs may contain a careful mixture of standard inputs: short text entry, long-form text areas, and drop-down menus. More are on the way.

Implementation overview

Most developers building and handling dialogs will follow steps similar to these:

  1. Build an interactive message, a slash command, or both. Dialogs cannot open until users interact with buttons, menus, or slash commands.
  2. As users interact or invoke commands, look for a trigger_id in the command invocation or interactive action payload.
  3. Use dialog.open to initiate a dialog in context with the user, providing a trigger_id and desired form elements.
  4. Once completed, results are sent to your application's interactive Request URL.
  5. Your app posts the results to a channel or provides some other submission confirmation message.

Preparing apps for dialogs

You'll need to create, configure, and install a Slack app before getting started with dialogs. Legacy custom integrations are not supported.

At minimum, you must configure the Interactive Components section of app management. Follow the UI's instructions to provide a Request URL to receive form submissions and other interactions.

If you will use a slash command to initiate dialogs, you will also need to configure your app's slash command.

It is necessary to reinstall your app after adding features and capabilities.

Interactive triggers

A dialog cannot be invoked without first being initiated by a message interaction or a slash command. That means a user needs to interact with a message button, message menu, or slash command provided by your app before they can engage with any dialog experiences your app provides.

Slack attaches a trigger_id value as part of all interaction payloads you receive, which acts as a pointer to a specific moment in the space-Slack-time continuum where a user interacted with your app.

Here's an example of an interactive message action containing a trigger_id:

{
    "actions": [
      {
        "name": "channels_list",
        "selected_options": [
          {
          "value": "C123ABC456"
          }
        ]
      }
    ],
    "callback_id": "select_simple_1234",
    "team": {
      "id": "T0123ABC456",
      "domain": "pocket-calculator"
    },
    "channel": {
      "id": "C0123ABC456",
      "name": "general"
    },
    "user": {
      "id": "U123ABC456",
      "name": "musik"
    },
    "action_ts": "1481579588.685999",
    "message_ts": "1481579582.000003",
    "attachment_id": "1",
    "token": "iUeRJkkRC9RMMvSRTd8gdq2m",
    "response_url": "https://hooks.slack.com/actions/T0123ABC456/123456789/JpmK0yzoZDeRiqfeduTBYXWQ",
    "trigger_id": "13345224609.738474920.8088930838d88f008e0"
}

And here's an example of a slash command execution containing a trigger_id:

token=gIkuvaNzQIHg97ATvDxqgjtO
team_id=T0001
team_domain=example
enterprise_id=E0001
enterprise_name=Globular%20Construct%20Inc
channel_id=C123ABC456
channel_name=test
user_id=U123ABC456
user_name=Steve
command=/weather
text=94070
response_url=https://hooks.slack.com/commands/1234/5678
trigger_id=13345224609.738474920.8088930838d88f008e0

These interactions are the inciting event to your app opening a dialog. The trigger_id is the key to unlock your app's momentary, focused dialog functionality. Because the trigger_id expires in 3 seconds, you must exchange the trigger to open a dialog in the given time interval. Using an expired trigger causes the trigger_expired error.

Use dialog.open soon after receiving a trigger_id. Triggers expire 3 seconds after being issued to your app.

Opening a dialog

To begin a modal dialog, call the dialog.open method.

As with all of our Web API methods, dialog.open typically takes URL-encoded parameters as arguments. We also support posting JSON.

Opening dialogs with JSON

The easiest way to open a dialog is to send a POST with a Content-type HTTP header set to application/json and a raw POST body containing your dialog's JSON presented as the dialog argument:

{
  "trigger_id": "13345224609.738474920.8088930838d88f008e0",
  "dialog": {
    "callback_id": "ryde-46e2b0",
    "title": "Request a Ride",
    "submit_label": "Request",
    "notify_on_cancel": true,
    "state": "Limo",
    "elements": [
        {
            "type": "text",
            "label": "Pickup Location",
            "name": "loc_origin"
        },
        {
            "type": "text",
            "label": "Dropoff Location",
            "name": "loc_destination"
        }
    ]
  }
}

See this section on HTTP POST bodies for any needed instruction.

Opening dialogs with URL-encoded JSON

A more complicated way to open dialogs is by sending your carefully crafted JSON as an application/x-www-form-urlencoded query parameter.

Similar to chat.postMessage, chat.unfurl, and chat.update this method also includes a parameter that expects a JSON object encoded with application/x-www-form-urlencoded.

A form you might create could be modeled in JSON as:

{
    "callback_id": "ryde-46e2b0",
    "title": "Request a Ride",
    "submit_label": "Request",
    "notify_on_cancel": true,
    "state": "Limo",
    "elements": [
        {
            "type": "text",
            "label": "Pickup Location",
            "name": "loc_origin"
        },
        {
            "type": "text",
            "label": "Dropoff Location",
            "name": "loc_destination"
        }
    ]
}

To prepare that as a HTTP POST to dialog.open, you'd optionally minify and then URL encode that JSON to a single string, displayed below as the value for the dialog POST body parameter.

POST /api/dialog.open
Authorization: Bearer xoxb-such-and-such
Content-type: application/x-www-form-urlencoded

trigger_id=13345224609.738474920.8088930838d88f008e0&dialog=%7B%22callback_id%22%3A%22ryde-46e2b0%22%2C%22title%22%3A%22Request%20a%20Ride%22%2C%22submit_label%22%3A%22Request%22%2C%22notify_on_cancel%22%3Atrue%2C%22state%22%3A%22Limo%22%2C%22elements%22%3A%5B%7B%22type%22%3A%22text%22%2C%22label%22%3A%22Pickup%20Location%22%2C%22name%22%3A%22loc_origin%22%7D%2C%7B%22type%22%3A%22text%22%2C%22label%22%3A%22Dropoff%20Location%22%2C%22name%22%3A%22loc_destination%22%7D%5D%7D

If all is well, you'll get a clean HTTP 200 OK response with an application/json body declaring:

{
  "ok": true
}

Error handling

If your dialog parameter or other aspects of your dialog are invalid, detailed errors are provided to help aid you in correcting them. See dialog.open for full detail on error conditions.

Top-level dialog attributes

Your dialog is presented to users stylishly with your carefully chosen title and curated form elements.

By default, all form elements are required. Use the optional field to make an element non-mandatory.

Attribute Type Description
title String User-facing title of this entire dialog. 24 characters to work with and it's required.
callback_id String An identifier strictly for you to recognize submissions of this particular instance of a dialog. Use something meaningful to your app. 255 characters maximum. Don't use this ID to reference sensitive data; use the more expansive state parameter below for that. Absolutely required.
elements Array Up to 10 form elements are allowed per dialog. See elements below. Required.
state String An optional string that will be echoed back to your app when a user interacts with your dialog. Use it as a pointer to reference sensitive data stored elsewhere.
submit_label String User-facing string for whichever button-like thing submits the form, depending on form factor. Defaults to Submit, localized in whichever language the end user prefers. 48 characters maximum, and may contain only a single word.
notify_on_cancel Boolean Default is false. When set to true, we'll notify your request URL whenever there's a user-induced dialog cancellation.

Dialog form elements

The current list of supported form elements includes:

  • text - Text inputs work well with concise free-form answers and inputs with unestablished bounds, such as names, email addresses, or ticket titles if your form is used for something like a bug tracker.
  • textarea - Text Areas are best when the expected answer is long — over 150 characters or so —. It is best for open-ended and qualitative questions.
  • select - Select menus are for multiple choice questions, and great for close-ended quantitative questions, such as office locations, priority level, meal preference, etc. The select elements may contain static menus or dynamically loaded menus specified with an optional data_source.

Text elements

Text elements are single-line plain text fields.

By default, all fields are required for a user to fill. Otherwise, the client validation will give the user an error. You can also set each field optional ("optional": true) and in this case, empty fields will submit as null.

Dialog text element

Example:

{
  "label": "Email Address",
  "name": "email",
  "type": "text",
  "subtype": "email",
  "placeholder": "you@example.com"
}

There is an optional subtype for the type: text. The value of the subtype can be set to either email, number, tel, or url, where the default is a plain text. Slack will ignore subtypes that don't match one of those options.

Setting the subtype is useful for mobile Slack clients where it will trigger special keyboards. For example, when a form field expects a phone number, you should use tel as a subtype so it invokes the numeric keypad.

Adding a subtype does not enforce any validation on the field it is added to.

Dialog text element subtypes

Text element attributes

Element Type Description
label String Label displayed to user. Required. 48 character maximum.
name String Name of form element. Required. No more than 300 characters.
type String The type of form element. For a text input, the type is always text. Required.
max_length Integer Maximum input length allowed for element. Up to 150 characters. Defaults to 150.
min_length Integer Minimum input length allowed for element. Up to 150 characters. Defaults to 0.
optional Boolean Provide true when the form element is not required. By default, form elements are required.
hint String Helpful text provided to assist users in answering a question. Up to 150 characters.
subtype String A subtype for this text input. Accepts email, number, tel, or url. In some form factors, optimized input is provided for this subtype.
value String A default value for this field. Up to 150 characters.
placeholder String A string displayed as needed to help guide users in completing the element. 150 character maximum.

Textarea elements

A textarea is a multi-line plain text editing control. You've likely encountered these on the world wide web. Use this element if you want a relatively long answer from users. The element UI provides a remaining character count to the max_length you have set or the default, 3000.

Dialog textarea element

Like text, this element supports subtype values of email, number, url, and tel.

An example:

{
  "label": "Additional information",
  "name": "comment",
  "type": "textarea",
  "hint": "Provide additional information if needed."
}

Textarea element attributes

Attribute Type Description
type String For a text area, the type is always textarea. It's required.
label String Label displayed to user. Required. No more than 48 characters.
name String Name of form element. Required. No more than 300 characters.
placeholder String A string displayed as needed to help guide users in completing the element. 150 character maximum.
max_length Integer Maximum input length allowed for element. 0-3000 characters. Defaults to 3000.
min_length Integer Minimum input length allowed for element. 1-3000 characters. Defaults to 0.
optional Boolean Provide true when the form element is not required. By default, form elements are required.
hint String Helpful text provided to assist users in answering a question. Up to 150 characters.
subtype String A subtype for this text area, just in case you need a lot of space for them. email, number, tel, or url
value String A default value for this field. Up to 3000 characters.

Select elements

Use the select element for multiple choice selections allowing users to pick a single item from a list. True to web roots, this selection is displayed as a dropdown menu.

Dialog select element

A select element may contain up to 100 selections, provided as an array of hashes (see below) in the form element's options field, (or an option_groups array).

These select elements have a data_source attribute which specifies whether you want a static list (static, which is the default) or a dynamic list (see below), but let's take a look at a static list first:

Example select form element definition:

{
  "label": "Meal preferences",
  "type": "select",
  "name": "meal_preferences",
  "options": [
    {
      "label": "Vegan",
      "value": "vegan"
    },
    {
      "label": "Kosher",
      "value": "kosher"
    },
    {
      "label": "Just put it in a burrito",
      "value": "burrito"
    } 
  ]
}

Example select form element with option_groups:

{
  "label": "Choose a meme",
  "name": "animal",
  "type": "select",
  "option_groups": [
    {
      "label": "Cats",
      "options": [
        {
          "label": "Maru",
          "value": "maru"
        },
        {
          "label": "Lil Bub",
          "value": "lilbub"
        },
        {
          "label": "Hamilton the Hipster Cat",
          "value": "hamilton"
        }
      ]
    },
    {
      "label": "Dogs",
      "options": [
        {
          "label": "Boo the Pomeranian",
          "value": "boo"
        }
      ]
    },
  ]
}
Dialog select element with option groups



Populate a select menu dynamically

In addition to the static select menu, you can also generate a data set for a menu on the fly. Make dialog select menus more dynamic by specifying one of these four data_source types:

Data source type Description
users The element will be populated with options corresponding to the users available to the current user.
channels The element will be populated with options corresponding to the public channels available to the current user.
conversations The element will be populated with options corresponding to the public channels, private channels, DMs, and MPIMs available to the current user.
external The element will be populated with options or options groups return from an endpoint specified in your app configuration setting.

If no data_source is specified, it defaults to static.

👤 Dynamic user list

Now you can easily populate a select menu with a list of users. For example, when you are creating a bug tracking app, you want to include a field for an assignee. Slack pre-populates the user list in client-side, so your app doesn't need access to a related OAuth scope.

Example select element with users data source:

{
  "label": "Assignee",
  "name": "bug_assignee",
  "type": "select",
  "data_source": "users"
}
Dynamic select from user list
#️⃣Channels and conversations

You can also provide a select menu with a list of channels. Specify your data_source as channels to limit only to public channels or use conversations to include private channels, direct messages, MPIMs, and whatever else we consider a conversation-like thing.

Example select element with conversations data source:

{
  "label": "Post this message on",
  "name": "channel_notify",
  "type": "select",
  "data_source": "conversations"
}
Dynamic select from channels
📄 Dynamic data from an external source

A list of options can be loaded from an external URL and used in your dialog menus.

First a little bit of setup is required to configure the URL that the options will be loaded from:

  • Go to the app settings page
  • Pick your app
  • Navigate to the Interactive Components section
  • Insert the Options Load URL in the Message Menus section

If you've already configured an Options Load URL for other uses, you'll be able to use the type and callback_id as below to determine which options data you want to return.

Now you can create a dialog with a select menu where the data_source is set to external:

{
  "label": "Bug ticket",
  "name": "ticket_list",
  "type": "select",
  "data_source": "external"
}

As soon as a user clicks or taps the drop-down menu, a request like the following is sent to your specified URL:

{
  "type": "dialog_suggestion",
  "token": "W3VDvuzi2nRLsiaDOsmJranO",
  "action_ts": "1528203589.238335",
  "team": {
    "id": "T24BK35ML",
    "domain": "hooli-hq"
  },
  "user": {
    "id": "U900MV5U7",
    "name": "gbelson"
  },
  "channel": {
    "id": "C012AB3CD",
    "name": "triage-platform"
  },
  "name": "external_data",
  "value": "",
  "callback_id": "bugs"
}

From the external URL, a list of options (or an option_groups array) should be returned as a HTTP 200 response:

Example options from the external data source:

{
  "options": [
    {
      "label": "[UXD-342] The button color should be artichoke green, not jalapeño",
      "value": "UXD-342"
    },
    {
      "label": "[FE-459] Remove the marquee tag",
      "value": "FE-459"
    },
    {
      "label": "[FE-238] Too many shades of gray in master CSS",
      "value": "FE-238"
    }
  ]
}

Example option_groups from the external data source:

{
  "option_groups": [
    {
      "label": "Visual Design",
      "options": [
        {
          "label": "[UXD-342] The button color should be artichoke green, not jalapeño",
          "value": "UXD-342"
        }
      ]
    },
    {
      "label": "Front-End Engineering",
      "options": [
        {
          "label": "[FE-459] Remove the marquee tag",
          "value": "FE-459"
        },
        {
          "label": "[FE-238] Too many shades of gray in master CSS",
          "value": "FE-238"
        }
      ]
    }
  ]
}

A maximum of 100 options may be included.

Please note that dialog menus uses the more clearly labeled field label instead of text for menu option labels.

Using Typeahead style select menus

When you're loading in external objects data for a menu, you can also adjust your code to provide a typeahead style experience where the user types the first few characters of a potential selection, and the app responds with a filtered list of results.

As users type into the select menu, Slack dispatches data to your app and by analyzing their text, you can return the most relevant, filtered options for them. Here's a data payload similar to the one that is sent to your app, showing a situation where the user has typed "des":

{
  "type": "dialog_suggestion",
  "team": {
    "id": "T123ABC456",
    "domain": "hooli-hq"
  },
  "user": {
    "id": "U123ABC456",
    "name": "sbutterfield"
  },
  "channel": {
    "id": "C123ABC456",
    "name": "triage-platform"
  },
  "action_ts": "1520635427.671963",
  "token": "W3VDvuzi2nRLsiaDOsmJranO",
  "name": "external_data",
  "value": "des",
  "callback_id": "bugs"
}

When you're enabling this kind of interaction, it's recommended that you set a min_query_length attribute to a sane value (at least 2). Otherwise your app might end up searching a massive list of potential matches for a single character, and your DB admins will yell at you.

Setting default values for select menus

To set a default selection for a select menu with a data_source attribute of static, users, channels, or conversations, provide a value attribute containing one of the value attributes from the element's collection of options:

{
  "label": "Meal preferences",
  "type": "select",
  "name": "meal_preferences",
  "value": "vegan",
  "options": [
    {
      "label": "Vegan",
      "value": "vegan"
    },
    {
      "label": "Kosher",
      "value": "kosher"
    },
    {
      "label": "Just put it in a burrito",
      "value": "burrito"
    } 
  ]
}

To set a default selection for a select menu with the data_source type set to external, instead provide a selected_options attribute, which should be an array containing a single label and value object that you know is in the dynamic options list:

{
  "label": "Bug ticket",
  "name": "ticket_list",
  "type": "select",
  "data_source": "external",
  "min_query_length": 2,
  "selected_options": [
    {
      "label": "[FE-459] Remove the marquee tag",
      "value": "FE-459"
    }
  ]
}

Attributes for static and dynamic select elements

<tr>
  <td><code>options</code></td>
  <td>Array</td>
  <td>Provide up to 100 options. Either <code>options</code> or <code>option_groups</code> is required for the <code>static</code> and <code>external</code>.

  <code>options[].label</code> is a user-facing string for this option. 75 characters maximum. Required.<br>
  <code>options[].value</code> is a string value for your app. If an integer is used, it will be parsed as a string. 75 characters maximum. Required.
  </td>
</tr>
<tr>
  <td><code>option_groups</code></td>
  <td>Array</td>
  <td>An array of objects containing a <code>label</code> and a list of options. Provide up to 100 option groups. Either <code>options</code> or <code>option_groups</code> is required for the <code>static</code> and <code>external</code>.<br><br>

  <code>options_groups[].label</code> is a user-facing string for this option. 75 characters maximum. Required.<br>
  <code>options_groups[].options</code> is an array that contains a list of options. It is formatted like the options array (see <code>options</code>).
  </td>
</tr>
Attribute Type Description
label String Label displayed to user. Required. No more than 48 characters.
name String Name of form element. Required. No more than 300 characters.
type String Set this to select for select elements.
data_source String Set this to either users, channels, conversations, or external. Default value is static.
min_query_length Number Specify the number of characters that must be typed by a user into a dynamic select menu before dispatching to the app.
placeholder String A string displayed as needed to help guide users in completing the element. 150 character maximum.
optional Boolean Provide true when the form element is not required. By default, form elements are required.
value String Provides a default selected value for select menus with a data_source of type static, users, channels, or conversations (see above).
This option is invalid with data_source of type external, where you must use selected_options as below.
selected_options Array Provides a default selected value for dynamic select menus with a data_source of type external. This should be an array containing a single object that specifies the default label and value (see above).

Dialog submission sequence

The typical submission workflow is:

  1. When users submit a form, Slack will validate their responses against the dialog's configuration.
  2. When the form is successfully submitted, Slack will send a request to your Request URL with the callback_id you set at dialog creation and the values submitted by the user.
  3. Your app has the opportunity to validate the user's responses according to your own business logic and suggest additional corrections before final submission.
  4. When the dialog is fully submitted, your app should display some kind of result or feedback to users.

Upon receiving this payload, your server must respond within 3 seconds, whether the form is valid or not. Otherwise, use the response_url to POST a delayed response.

Evaluating submission responses

Your "Request URL" (formerly known as an "Action URL"), configured in your application management settings under Interactive Components, will receive an interactive framework JSON structure in a URL-encoded payload POST parameter.

For example, consider this submission:

POST https://example.com/your-request-url
payload=%7B%22type%22%3A%22dialog_submission%22%2C%22submission%22%3A%7B%22name%22%3A%22Sigourney%20Dreamweaver%22%2C%22email%22%3A%22sigdre%40example.com%22%2C%22phone%22%3A%22%2B1%20800-555-1212%22%2C%22meal%22%3A%22burrito%22%2C%22comment%22%3A%22No%20sour%20cream%20please%22%2C%22team_channel%22%3A%22C0LFFBKPB%22%2C%22who_should_sing%22%3A%22U0MJRG1AL%22%7D%2C%22callback_id%22%3A%22employee_offsite_1138b%22%2C%22state%22%3A%22vegetarian%22%2C%22team%22%3A%7B%22id%22%3A%22T1ABCD2E12%22%2C%22domain%22%3A%22coverbands%22%7D%2C%22user%22%3A%7B%22id%22%3A%22W12A3BCDEF%22%2C%22name%22%3A%22dreamweaver%22%7D%2C%22channel%22%3A%7B%22id%22%3A%22C1AB2C3DE%22%2C%22name%22%3A%22coverthon-1999%22%7D%2C%22action_ts%22%3A%22936893340.702759%22%2C%22token%22%3A%22M1AqUUw3FqayAbqNtsGMch72%22%2C%22response_url%22%3A%22https%3A%2F%2Fhooks.slack.com%2Fapp%2FT012AB0A1%2F123456789%2FJpmK0yzoZDeRiqfeduTBYXWQ%22%7D

Decode the payload parameter's JSON and you have a mapping with a few notable keys:

{
    "type": "dialog_submission",
    "submission": {
        "name": "Sigourney Dreamweaver",
        "email": "sigdre@example.com",
        "phone": "+1 800-555-1212",
        "meal": "burrito",
        "comment": "No sour cream please",
        "team_channel": "C123ABC456",
        "who_should_sing": "U123ABC456"
    },
    "callback_id": "employee_offsite_1138b",
    "state": "vegetarian",
    "team": {
        "id": "T123ABC456",
        "domain": "coverbands"
    },
    "user": {
        "id": "W123ABC456",
        "name": "dreamweaver"
    },
    "channel": {
        "id": "C123ABC456",
        "name": "coverthon-1999"
    },
    "action_ts": "936893340.702759",
    "token": "M1AqUUw3FqayAbqNtsGMch72",
    "response_url": "https://hooks.slack.com/app/T012AB0A1/123456789/JpmK0yzoZDeRiqfeduTBYXWQ"
}

Let's look deeper at those attributes, which you might recognize many of from interactive messages.

  • type - to differentiate from other interactive components, look for the string value dialog_submission.
  • submission - a hash of key/value pairs representing the user's submission. Each key is a name field your app provided when composing the form. Each value is the user's submitted value, or in the case of a static select menu, the value you assigned to a specific response. The selection from a dynamic menu, the value can be a channel ID, user ID, etc.
  • callback_id - this value is the unique callback_id identifier your app gave this instance of the dialog. Use the callback_id to keep track of which form submission is which, not to store state.
  • state - this string echoes back what your app passed to dialog.open. Use it as a pointer that references sensitive data stored elsewhere.
  • team - this hash contains the id and name of the workspace from which this interaction occurred
  • user - this hash contains the id and name of the user who completed the form
  • channel - this hash contains the id and name of the channel or conversation where this dialog was completed
  • action_ts - this is a unique identifier for this specific action occurrence generated by Slack. It can be evaluated as a timestamp with milliseconds if that is helpful to you.
  • token - this is a deprecated verification token shared between your app and Slack, used to check that an incoming request originates from Slack.
  • response_url - the URL can be used to post responses to dialog submissions. Read our guide to app interactivity to learn more about how to respond using a response_url.

The token value, which represents a verification token, is now deprecated. The best way to verify the authenticity of a Slack request is to use the signing secret provided to your app.

Using state to pass extra information through dialogs

When your app calls dialog.open, it may optionally pass additional data via the state parameter. The state string may be up to 3,000 characters long, and it isn't shown to users. During dialog submission and cancellation, the state string is echoed back to your app. Whatever stash of pointers you passed during dialog.open gets returned to you, unharmed and unchanged.

Note: don't store sensitive information in the state field. It's best to use state to reference sensitive data stored elsewhere. You may already be using the callback_id parameter for that, but we strongly recommend using the state field instead.

Input validation

While Slack will handle some client-side validation of user input upon submission, we encourage your app to do its own round of validation based on its own business logic.

As soon as your user hits the submit button, Slack client will validate the user's inputs against the validation parameters that you have passed to make sure that all the required fields are filled and the formats are correct. And the form won't submit until the user corrects all errors.

Dialog client validation

You should validate the submission values you receive server-side against your own heuristics.

If your app finds any errors with the submission, respond with an application/json payload within the body of a 200 OK response - the requests between your app and Slack are still OK after all, so don't use any kind of error response.

This payload should be an errors array containing 1 or more objects that include:

  • name - a string which specifies the corresponding dialog element that is being rejected. This must match the name used to create that element.
  • error - a string which describes why that element is being rejected.

Here's an example of what that payload should look like:

{
  "errors": [
    {
      "name": "email_address",
      "error": "Sorry, this email domain is not authorized!"
    },
    {
      "name": "username",
      "error": "Uh-oh. This username has been taken!"
    }
  ]
}

The API returns these errors to the user in-app, allowing the user to make corrections and submit again.

When there are no exceptions within the dialog submission, your app must respond with 200 OK with an empty body. This will complete the dialog.

Following up

After you've confirmed the submission is valid and sent a HTTP 200 OK message, you'll want to let the user know everything is fine.

Use a method like chat.postMessage or chat.postEphemeral, depending on context and desired visibility, to create a message thanking the user and/or providing other feedback. You can also send a delayed response to the response_url if you need more than 3 seconds to respond. with the response_url, your app can continue interacting with users up to 5 times within 30 minutes of the action invocation.

For example, if you are building an app for survey, it is still a good idea to send a follow-up message to make sure your valuable user knows the form submission was successful!

Read our guide to app interactivity to learn more about sending responses.

Dialog cancellations

When users dismiss a dialog by either clicking on the "Cancel" button, exit "x" on the top-right corner, or by clicking away from a dialog, we'll warn them that they'll lose any answers they've made to an unsubmitted dialog.

Once the dialog is canceled, Slack can send you a dialog_cancellation events to your Request URL, if you wish. To receive the notification, set the notify_on_cancel value true, when you open the dialog.

If you wish to follow up with the user, use the cancellation's response_url to create a new message using response URL semantics.

{
  "type": "dialog_cancellation",
  "token": "old-and-moldy-verification-token",
  "action_ts": "1542993577.333025",
  "team": {
    "id": "T123ABC456",
    "domain": "coverbands"
  },
  "user": {
    "id": "U123ABC456",
    "name": "leepresson"
  },
  "channel": {
    "id": "C123ABC456",
    "name": "graceland"
  },
  "callback_id": "best_elvis_impersonator_name",
  "response_url": "https://hooks.slack.com/app/T123ABC456/486xxxxxx/jbF9HF",
  "state": "final_round"
}

Adapting existing workflows to dialogs

Existing workflows using only conversation, message buttons, or message menus can be enhanced with the focused concentration made possible with dialogs.

Imagine a /helpdesk slash command connected to a company's internal IT helpdesk. With very limited syntax enforcement and the wild possibilities inherent in free-form text, filing a ticket is imprecise and less instantly actionable.

/helpdesk hardware "help my cat chewed on my mouse cable it doesn't work anymore"

Commands like this are noble in purpose but could be made more precise in execution with dialogs.

Your software could easily interpret this as a ticket meant for a hardware category. With some heuristics and analysis, your app might derive that it's about a mouse.

Without asking a series of focused questions to illuminate the entire surface area of the user's inquiry, the user's submission is significantly less actionable than its potential.

By invoking a dialog, you may ask specific guided questions around urgency, platform, location, and other nuances made possible with multiple choice.

Another example, illustrated in the animation below, demonstrates a slash command execution leading to an optimized dialog and useful outcome.

Demo of a slash command leading to a dialog submission

Desiging useful dialogs

In general, the easier it is to enter information into a dialog, the more likely your users are to fill them out to completion. Wherever possible, favor structured inputs over freeform text, and work to keep the total length relatively short.

In order to look best on mobile as well as desktop, we suggest that modal titles be short. Our tips on choosing words for messages generally apply: keep modal titles sentence-cased; pick action verbs for buttons that confirm what the user is doing.

Provide a placeholder value for all field elements to better communicate purpose independent of the user's interface.

These are best used for concise answers with established bounds or options, e.g. office location, age range, or dietary preference. Keep dropdown options short, and remember to test how they render on mobile devices.

Text input

Text input fields are capped at 150 characters, and are best used for concise freeform answers, like name or ticket title.

If inputs are better served with pre-selected answers (e.g. age ranges, department), consider using a dropdown. If you expect input to be longer than 150 characters, consider using a text area.

Text area

Text areas are good for open-ended questions that need more room, like meeting notes or interview feedback. Most dialogs should not have more than one or two text areas; remember that keeping the input brief and contextual will help ensure that people complete the forms.

Like this:

A well-formulated dialog

Not like this:

A ill-formulated dialog

Your dialogs can be short and to the point, a focused way to collect a single piece of information. They can be long, asking questions requiring more thought, research, and careful response. Chain dialogs together with buttons or menus to serialize phased workflow execution.