You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: site/content/integrate/plugins/interactive-dialogs/_index.md
+97-68Lines changed: 97 additions & 68 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,8 +36,7 @@ Interactive dialogs support the following parameters:
36
36
|`submit_label`| String | (Optional) Label of the button to complete the dialog. Default is `Submit`. |
37
37
|`notify_on_cancel`| Boolean | (Optional) When `true`, sends an event back to the integration whenever there's a user-induced dialog cancellation. No other data is sent back with the event. Default is `false`. |
38
38
|`state`| String | (Optional) String provided by the integration that will be echoed back with dialog submission. Default is the empty string. |
39
-
|`is_multistep`| Boolean | (Optional) Set to `true` to enable multi-step dialog functionality. Default is `false`. |
40
-
|`refresh_on_select`| Boolean | (Optional) When `true`, sends field refresh requests when select field values change. Default is `false`. |
39
+
|`source_url`| String | (Optional) URL for field refresh requests. When a select element with `refresh: true` changes value, Mattermost sends a refresh request to this URL. Also used as the endpoint for multi-step form responses. |
41
40
42
41
Sample JSON is given below. Form submissions are sent back to the URL defined by the integration. You must also include the trigger ID you received from the slash command or interactive message.
43
42
@@ -53,8 +52,7 @@ Sample JSON is given below. Form submissions are sent back to the URL defined by
53
52
"submit_label": "<label of the button to complete the dialog>",
54
53
"notify_on_cancel": false,
55
54
"state": "<string provided by the integration that will be echoed back with dialog submission>",
56
-
"is_multistep": false,
57
-
"refresh_on_select": false
55
+
"source_url": "<optional URL for field refresh and multi-step form handling>"
58
56
}
59
57
}
60
58
```
@@ -218,17 +216,27 @@ To use dynamic select, set `data_source: "dynamic"` and provide a `data_source_u
218
216
}
219
217
```
220
218
221
-
When users interact with a dynamic select field, Mattermost will send HTTP POST requests to your `data_source_url` with the following payload:
219
+
When users interact with a dynamic select field, Mattermost sends an HTTP POST request to the `/api/v4/actions/dialogs/lookup` endpoint, which proxies the request to your `data_source_url`. The payload uses the same `SubmitDialogRequest` structure:
222
220
223
221
```json
224
222
{
225
-
"user_id": "user_id_here",
226
-
"channel_id": "channel_id_here",
227
-
"team_id": "team_id_here",
228
-
"term": "user_search_input"
223
+
"type": "dialog_lookup",
224
+
"url": "<your data_source_url>",
225
+
"callback_id": "<callback ID>",
226
+
"state": "<state>",
227
+
"user_id": "<user ID>",
228
+
"channel_id": "<channel ID>",
229
+
"team_id": "<team ID>",
230
+
"submission": {
231
+
"query": "user_search_input",
232
+
"selected_field": "dynamic_field",
233
+
"other_field_name": "current_value"
234
+
}
229
235
}
230
236
```
231
237
238
+
The `submission` map contains `query` (the user's search input), `selected_field` (the field being searched), and current values of all other form fields.
239
+
232
240
Your endpoint should respond with an array of options:
233
241
234
242
```json
@@ -239,16 +247,16 @@ Your endpoint should respond with an array of options:
239
247
"value": "option1"
240
248
},
241
249
{
242
-
"text": "Option 2",
250
+
"text": "Option 2",
243
251
"value": "option2"
244
252
}
245
253
]
246
254
}
247
255
```
248
256
249
257
**Security Requirements:**
250
-
-Dynamic select URLs must use HTTPS
251
-
-URLs must be within the `/plugins/` path for security
258
+
-External URLs must use HTTPS
259
+
-Plugin paths (starting with `/plugins/`) are proxied locally and do not require HTTPS
252
260
- The lookup endpoint should validate user permissions
253
261
254
262
**Dynamic select with multiselect:**
@@ -322,7 +330,8 @@ The list of supported fields for the `select` type element is included below:
322
330
|`type`| String | Set this value to `select` for a `select` element. |
323
331
|`multiselect`| Boolean | (Optional) Set to `true` to allow multiple selections from the list. Default is `false`. |
324
332
|`data_source`| String | (Optional) One of `users`, `channels`, or `dynamic`. If none specified, assumes a manual list of options is provided by the integration. |
325
-
|`data_source_url`| String | (Optional) URL for dynamic option loading when `data_source` is `dynamic`. Must be HTTPS and within `/plugins/` path. |
333
+
|`data_source_url`| String | (Optional) URL for dynamic option loading when `data_source` is `dynamic`. Must be HTTPS or a `/plugins/` path. |
334
+
|`refresh`| Boolean | (Optional) When `true`, triggers a field refresh request to the dialog's `source_url` when this field's value changes. Use this for dependent dropdowns or conditional form fields. Default is `false`. |
326
335
|`optional`| Boolean | (Optional) Set to `true` if this form element is not required. Default is `false`. |
327
336
|`options`| Array | (Optional) An array of options for the select element. Not applicable for `users`, `channels`, or `dynamic` data sources. |
328
337
|`help_text`| String | (Optional) Set help text for this form element. Maximum 150 characters. |
@@ -613,48 +622,45 @@ Finally, once the request is submitted, we recommend that the integration respon
613
622
## Multi-step dialogs
614
623
##### Minimum Server Version: 11.1
615
624
616
-
Multi-step dialogs enable wizard-like form experiences where users can navigate through multiple steps to complete a complex workflow. Each step can have different fields, and form values are preserved as users progress through the steps.
625
+
Multi-step dialogs enable wizard-like form experiences where users progress through multiple steps to complete a complex workflow. Each step can have different fields, and form values are accumulated across steps.
617
626
618
-
To enable multi-step functionality, set `is_multistep: true` in your dialog configuration.
627
+
Multi-step behavior is achieved by having your submit handler return a new form instead of closing the dialog. No special dialog property is required.
619
628
620
629
### Multi-step workflow
621
630
622
-
1.**Initial dialog**: Open with `is_multistep: true` and provide the first step's elements
623
-
2.**Step navigation**: Users can navigate between steps using Next/Previous buttons
624
-
3.**State preservation**: Form values are automatically preserved across steps
625
-
4.**Final submission**: Submit the completed form with all step data
631
+
1.**Initial dialog**: Open a normal dialog with the first step's elements. Use the `state` field to track which step you're on (e.g., `"step_1"`).
632
+
2.**Step submission**: When the user clicks Submit, your integration receives a standard `dialog_submission` request with `type: "dialog_submission"`.
633
+
3.**Return next step**: Instead of returning an empty response (which closes the dialog), return a response with `type: "form"` and a `form` object containing the next step's dialog definition.
634
+
4.**Value accumulation**: The client automatically accumulates submission values across steps. Each step's submission includes all values from previous steps plus the current step.
635
+
5.**Final submission**: On the last step, return an empty response (HTTP 200) or `{"type": "ok"}` to close the dialog.
626
636
627
637
### Multi-step submission handling
628
638
629
-
When a user submits a multi-step dialog, your integration will receive webhook calls to handle step transitions and final submission:
630
-
631
-
#### Step transition webhook
632
-
633
-
When users navigate between steps, Mattermost sends a request to your configured URL with the following payload:
639
+
Each step submission uses the standard dialog submission format:
634
640
635
641
```json
636
642
{
637
-
"type": "dialog_multistep",
638
-
"callback_id": "<callback ID provided by the integration>",
639
-
"state": "<state provided by the integration>",
643
+
"type": "dialog_submission",
644
+
"callback_id": "<callback ID>",
645
+
"state": "step_1",
640
646
"user_id": "<user ID>",
641
647
"channel_id": "<channel ID>",
642
648
"team_id": "<team ID>",
643
-
"step": 2,
644
-
"direction": "next",
645
649
"submission": {
646
-
"field_name_from_step_1": "value",
647
-
"field_name_from_step_2": "value"
648
-
}
650
+
"step1_field": "value_from_step_1"
651
+
},
652
+
"cancelled": false
649
653
}
650
654
```
651
655
652
-
Your integration should respond with the next step's dialog configuration:
656
+
To advance to the next step, your integration responds with a new form:
653
657
654
658
```json
655
659
{
656
-
"dialog": {
657
-
"title": "Step 2 of 3",
660
+
"type": "form",
661
+
"form": {
662
+
"callback_id": "multistep_wizard",
663
+
"title": "Setup Wizard - Step 2 of 3",
658
664
"elements": [
659
665
{
660
666
"display_name": "Step 2 Field",
@@ -663,20 +669,18 @@ Your integration should respond with the next step's dialog configuration:
663
669
}
664
670
],
665
671
"submit_label": "Next",
666
-
"state": "updated_state_for_step_2"
672
+
"state": "step_2"
667
673
}
668
674
}
669
675
```
670
676
671
-
#### Final submission
672
-
673
-
The final step submission uses the same format as regular dialog submissions, but includes data from all steps:
677
+
On the final step, the submission includes accumulated values from all steps:
674
678
675
679
```json
676
680
{
677
681
"type": "dialog_submission",
678
682
"callback_id": "<callback ID>",
679
-
"state": "<final state>",
683
+
"state": "step_3",
680
684
"user_id": "<user ID>",
681
685
"channel_id": "<channel ID>",
682
686
"team_id": "<team ID>",
@@ -689,6 +693,8 @@ The final step submission uses the same format as regular dialog submissions, bu
689
693
}
690
694
```
691
695
696
+
Return an empty response (HTTP 200 with empty body) or `{"type": "ok"}` to close the dialog.
697
+
692
698
### Multi-step example
693
699
694
700
```json
@@ -699,7 +705,6 @@ The final step submission uses the same format as regular dialog submissions, bu
699
705
"callback_id": "multistep_wizard",
700
706
"title": "Setup Wizard - Step 1 of 3",
701
707
"introduction_text": "Welcome to the setup wizard",
702
-
"is_multistep": true,
703
708
"elements": [
704
709
{
705
710
"display_name": "Project Name",
@@ -719,67 +724,89 @@ The final step submission uses the same format as regular dialog submissions, bu
719
724
720
725
Interactive dialogs support dynamic field refresh functionality, allowing fields to be updated based on user input. This is useful for dependent dropdowns or conditional form fields.
721
726
722
-
To enable field refresh, set `refresh_on_select: true` in your dialog configuration. When users change select field values, Mattermost will send a field refresh request to your configured URL.
727
+
To enable field refresh, two things are required:
728
+
1. Set `refresh: true` on the **element** that should trigger refreshes when its value changes.
729
+
2. Set `source_url` on the **dialog** to specify where refresh requests are sent.
730
+
731
+
When a user changes a select element that has `refresh: true`, Mattermost sends a request to the dialog's `source_url` via the `/api/v4/actions/dialogs/submit` endpoint.
723
732
724
733
### Field refresh webhook
725
734
726
-
The field refresh webhook payload includes the current form state and the changed field:
735
+
The field refresh request uses the standard `SubmitDialogRequest` structure with `type` set to `"refresh"`:
727
736
728
737
```json
729
738
{
730
-
"type": "dialog_field_refresh",
739
+
"type": "refresh",
740
+
"url": "<source_url>",
731
741
"callback_id": "<callback ID>",
732
742
"state": "<current state>",
733
743
"user_id": "<user ID>",
734
744
"channel_id": "<channel ID>",
735
745
"team_id": "<team ID>",
736
-
"field_name": "changed_field_name",
737
746
"submission": {
738
-
"changed_field_name": "new_value",
739
-
"other_field": "existing_value"
747
+
"category": "software",
748
+
"subcategory": "",
749
+
"selected_field": "category"
740
750
}
741
751
}
742
752
```
743
753
744
-
Your integration should respond with updated field configurations:
754
+
The `submission` map contains the current values of all form fields, plus a `selected_field` key indicating which field triggered the refresh. Cleared fields are sent as empty strings.
755
+
756
+
Your integration should respond with a complete updated dialog wrapped in a `form` response:
745
757
746
758
```json
747
759
{
748
-
"elements": [
749
-
{
750
-
"display_name": "Updated Field",
751
-
"name": "dependent_field",
752
-
"type": "select",
753
-
"options": [
754
-
{
755
-
"text": "New Option 1",
756
-
"value": "new_opt1"
757
-
},
758
-
{
759
-
"text": "New Option 2",
760
-
"value": "new_opt2"
761
-
}
762
-
]
763
-
}
764
-
]
760
+
"type": "form",
761
+
"form": {
762
+
"callback_id": "dynamic_form",
763
+
"title": "Dynamic Form",
764
+
"source_url": "/plugins/your-plugin-id/refresh",
765
+
"elements": [
766
+
{
767
+
"display_name": "Category",
768
+
"name": "category",
769
+
"type": "select",
770
+
"refresh": true,
771
+
"options": [
772
+
{"text": "Software", "value": "software"},
773
+
{"text": "Hardware", "value": "hardware"}
774
+
]
775
+
},
776
+
{
777
+
"display_name": "Subcategory",
778
+
"name": "subcategory",
779
+
"type": "select",
780
+
"options": [
781
+
{"text": "Frontend", "value": "frontend"},
782
+
{"text": "Backend", "value": "backend"}
783
+
]
784
+
}
785
+
],
786
+
"submit_label": "Submit",
787
+
"state": "step_1"
788
+
}
765
789
}
766
790
```
767
791
792
+
The response replaces the entire dialog with the new form definition. The client preserves the user's current field values where field names match.
0 commit comments