Files
upstream-mirror 240db4e2a7
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

251 lines
12 KiB
Markdown

---
title: "Use markdown action buttons"
description: "Markdown action buttons let an integration turn an inline post-markdown link into an action affordance. Clicking the link dispatches a post action to the integration's endpoint instead of navigating away, expanding interactivity beyond message attachments."
sidebar_label: "Markdown action buttons"
sidebar_position: 45
---
:::note[Part of the Interactive Messages framework]
Markdown action buttons are one binding surface in the Interactive Messages framework alongside [Mattermost Blocks](/developers/integrate/reference/mm-blocks). All surfaces share the same `mm_blocks_actions` action registry.
:::
Use markdown action buttons to add inline, in-text affordances to a post — without using a message attachment. They're useful when:
- A short message reads naturally with an "Approve" or "Reject" inline link.
- An integration wants to mix narrative text and action affordances in the same post body.
- The visual weight of a full message attachment isn't warranted.
For block-style buttons and menus, see [Mattermost Blocks](/developers/integrate/reference/mm-blocks). For legacy attachment actions, see [interactive messages](/developers/integrate/plugins/interactive-messages).
## How it works
A markdown action has two parts:
1. A markdown link in the post body using the `mmaction://` scheme, where the link host is the action ID:
```text
[Approve](mmaction://approve?ticket=ISS-101)
```
2. A matching entry in the post's `props.mm_blocks_actions` registry that tells the server what to do when the link is clicked:
```json
{
"mm_blocks_actions": {
"approve": {
"type": "external",
"url": "https://integration.example.com/hook/approve",
"context": {"project": "Demo Project"}
}
}
}
```
The client renders the link as a button. Clicking it dispatches a request to the Mattermost server, which forwards the call to the integration's `url` along with merged query parameters and any server-side context.
## Example post payload
The following payload posts a message with two markdown action buttons. The body markdown references action IDs defined in `mm_blocks_actions`.
```json
{
"channel_id": "qmd5oqtwoibz8cuzxzg5ekshgr",
"message": "Ticket ISS-101 needs review: [Approve](mmaction://approve?ticket=ISS-101) [Reject](mmaction://reject?ticket=ISS-101)",
"props": {
"mm_blocks_actions": {
"approve": {
"type": "external",
"url": "https://integration.example.com/hook/approve",
"context": {"project": "Demo Project"}
},
"reject": {
"type": "external",
"url": "https://integration.example.com/hook/reject",
"context": {"project": "Demo Project"}
}
}
}
}
```
You can send this payload using the [create post REST API](https://api.mattermost.com/#operation/CreatePost), an [incoming webhook](/developers/integrate/webhooks/incoming), or from a [plugin](/developers/integrate/plugins/components/server).
### Submit using the REST API
```bash
curl -X POST $MM_URL/api/v4/posts \
-H "Authorization: Bearer $BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "'$CHANNEL_ID'",
"message": "Ticket ISS-101 needs review: [Approve](mmaction://approve?ticket=ISS-101) [Reject](mmaction://reject?ticket=ISS-101)",
"props": {
"mm_blocks_actions": {
"approve": {
"type": "external",
"url": "https://integration.example.com/hook/approve",
"context": {"project": "Demo Project"}
},
"reject": {
"type": "external",
"url": "https://integration.example.com/hook/reject",
"context": {"project": "Demo Project"}
}
}
}
}'
```
### Submit from a plugin
A server-side plugin creates posts with markdown actions through the existing `API.CreatePost` / `API.UpdatePost` interfaces. The action `url` typically points at the plugin's own HTTP handler.
```go
post := &model.Post{
ChannelId: channelID,
UserId: p.botID,
Message: "Ticket ISS-101 needs review: " +
"[Approve](mmaction://approve?ticket=ISS-101) " +
"[Reject](mmaction://reject?ticket=ISS-101)",
Props: model.StringInterface{
"mm_blocks_actions": map[string]any{
"approve": map[string]any{
"type": "external",
"url": fmt.Sprintf("/plugins/%s/inline_action/approve", manifest.Id),
"context": map[string]any{"project": "Demo Project"},
},
"reject": map[string]any{
"type": "external",
"url": fmt.Sprintf("/plugins/%s/inline_action/reject", manifest.Id),
"context": map[string]any{"project": "Demo Project"},
},
},
},
}
_, err := p.API.CreatePost(post)
```
A plugin can also change markdown actions later via `API.UpdatePost`. See [Updating and removing actions](#updating-and-removing-actions) for who may change `mm_blocks_actions` and how actions are removed.
## Link syntax
```text
[<label>](mmaction://<action_id>?<query_string>)
```
**`<label>`**<br/>
The link text rendered as the button label.
**`<action_id>`**<br/>
The host portion of the URL. Must match a key in `props.mm_blocks_actions`. Must contain only letters, numbers, underscores, or hyphens (`[A-Za-z0-9_-]+`), matched case-sensitively, and may be up to 64 characters long.
**`<query_string>`** (optional)<br/>
`key=value` pairs that are forwarded with the dispatched action and merged into the target URL's query string by the server. Link-supplied values override registry-supplied values on key conflict. Up to 50 entries; each key may be up to 128 characters and each value up to 2048 characters.
## The `mm_blocks_actions` registry
The `mm_blocks_actions` post prop is a map keyed by action ID. Each entry describes how the server should handle clicks on that action. The registry supports up to 50 action entries.
```json
{
"mm_blocks_actions": {
"<action_id>": {
"type": "<action_type>",
"url": "...",
"context": { ... },
"query": { ... }
}
}
}
```
| Field | Required | Description |
| --- | --- | --- |
| `type` | yes | Action type. See [Action types](#action-types) below. |
| `url` | depends on type | Target URL for the integration's callback endpoint. Required for `external`. |
| `context` | no | Object of server-side context values forwarded to the integration in the post-action request body. Not visible to the client. Up to 50 entries; each key may be up to 128 characters. |
| `query` | no | Static `string -> string` map merged into the target URL's query string by the server. Combined with any query parameters supplied in the `mmaction://` link — link values win on key conflict. Up to 50 entries; each key may be up to 128 characters and each value up to 2048 characters. |
### Action types
| Type | Behaviour |
| --- | --- |
| `external` | The server sends a POST request to `url` with the dispatched action, including `context`, `query` (after merging), and any link-supplied query parameters. The integration responds with a standard post-action response. |
Additional action types may be introduced as the broader Interactive Messages framework lands. Entries with an unknown `type` value are rejected at post-create time.
## Click dispatch flow
The diagram below describes the lifecycle of a single click on a markdown action button.
```mermaid
sequenceDiagram
participant Integration
participant Server as Mattermost server
participant Client
Integration->>Server: Create post (mmaction link + mm_blocks_actions)
Server->>Server: Validate post and store action registry
Server->>Client: Deliver post
Client->>Client: Render link as button
Client->>Server: POST /api/v4/posts/{id}/actions/{action_id}
Server->>Server: Merge query parameters into action URL
Server->>Integration: POST action callback
Integration->>Server: Post-action response
Server->>Client: Update post / ephemeral message
```
1. **Integration** creates a post with an `mmaction://<id>` link and a matching `mm_blocks_actions[<id>]` entry.
2. **Mattermost server** validates the post (`mm_blocks_actions` schema and limits) and stores it.
3. **Client** renders the link as a button. Clicking it dispatches `POST /api/v4/posts/{post_id}/actions/{action_id}` with the link's query string in the request body.
4. **Mattermost server** looks up `mm_blocks_actions[<id>]`, merges the registry's static `query` with the request body's query (request body wins on conflict), merges the result into the action URL's query string, and POSTs the integration endpoint.
5. **Integration** responds with a standard post-action response.
## Receiving action callbacks
When a user clicks a markdown action button, the Mattermost server sends an HTTP POST request to the `url` configured in the matching `mm_blocks_actions` entry. The request body follows the same `PostActionIntegrationRequest` shape used by [Mattermost Blocks](/developers/integrate/reference/mm-blocks) and legacy [message attachment](/developers/integrate/reference/message-attachments) buttons — the integration responds with the same post-action response format.
## Updating and removing actions
- An integration session (bot account, personal access token, or OAuth app) may add, replace, or remove `mm_blocks_actions` on a post **it authored**, via the update and patch post endpoints. Other sessions can edit the message but not another author's actions.
- `mm_blocks_actions` is an exception to the usual update rule that omitted fields are cleared: the server preserves the post's existing actions when an update leaves the prop out, so a message-only edit never wipes buttons. (Sending an empty or altered value still replaces them.)
- Removing a button's `mmaction://` link from the message revokes its action: the entry is pruned and later clicks return a not-found error.
## Validation limits
Posts that exceed any of the following limits are rejected at create or update time.
| Limit | Value |
| --- | --- |
| Maximum entries in `mm_blocks_actions` | 50 |
| Maximum length of an action ID (map key) | 64 characters |
| Action ID character set | `[A-Za-z0-9_-]+` |
| Maximum entries in `query` (link or registry) | 50 |
| Maximum length of a query key | 128 characters |
| Maximum length of a query value | 2048 characters |
## Error reference
The following error IDs may be returned by the post-action and post-create APIs when processing markdown action requests.
| Error ID | Cause |
| --- | --- |
| `api.post.do_action.query.app_error` | The query parameters supplied with the action click exceeded one of the limits described in [Link syntax](#link-syntax) or [The `mm_blocks_actions` registry](#the-mm_blocks_actions-registry). |
| `api.post.do_action.merge_query.app_error` | The server could not merge the supplied query parameters into the action's target URL — typically because the URL is malformed. |
## Security considerations
Markdown action buttons follow the same security model as message attachment actions:
- The action `url` is invoked server-to-server, never directly from the client.
- `context` values are server-only and are not exposed to the rendering client.
- Action IDs must match the pattern `[A-Za-z0-9_-]+` — alphanumerics, underscore, and hyphen — and are matched case-sensitively.
- Action entries with malformed or unknown `type` values are rejected at post-create time and never reach the click-dispatch path.
## See also
- [Mattermost Blocks](/developers/integrate/reference/mm-blocks) — block-based buttons, menus, and layout.
- [Interactive messages](/developers/integrate/plugins/interactive-messages) — overview and legacy attachment actions.
- [Message attachments](/developers/integrate/reference/message-attachments) — broader message format reference.
- [Incoming webhooks](/developers/integrate/webhooks/incoming) — submitting posts via webhooks.
- [REST API: create post](https://api.mattermost.com/#operation/CreatePost)