--- 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 [