Slack mrkdwn
Slack's custom mrkdwn syntax is not standard Markdown. First identify the receiving field; the same punctuation can mean something different on another Slack surface.
Choose the Rendering System
| System | Common surfaces | Example |
|---|---|---|
| Slack mrkdwn | Top-level message text (default), Block Kit text objects with type: "mrkdwn", composer/classic unfurl blocks, legacy attachment fields enabled by mrkdwn_in | *bold* <https://example.com\|link> |
| Standard Markdown | markdown block; markdown_text chat method arguments; Work Object string fields/comments with format: "markdown"; Work Object partial-view messages with message_format: "markdown" | **bold** [link](https://example.com) |
| Structured rich_text | Slack's WYSIWYG/user-message representation and rich_text_input values | Explicit nested JSON elements and style objects |
| plain_text | Labels, buttons, placeholders, view titles, and any text object that must render literally | No formatting syntax |
Do not translate by punctuation alone. For example, *bold* in mrkdwn is italic in standard Markdown. A rich_text block does not parse either syntax; construct its elements explicitly.
Standard Markdown Surfaces
Markdown Block
{
"type": "markdown",
"text": "## Result\n\n**Complete** — see [details](https://example.com)."
}
Slack's markdown block explicitly documents support for:
- bold, italic, nested bold/italic, and strikethrough
- ordered and unordered lists
- links, block quotes, inline code, and fenced code blocks
- language-tagged fenced code blocks with syntax highlighting
- headings, horizontal dividers, tables, and task lists
- images, translated to hyperlink text using the image alt text
- backslash escaping of documented special characters
The current block reference says all heading levels render at the same size, while Slack's March 6, 2026 changelog says variable-sized headers are being rolled out. Treat heading size as rollout- and client-dependent rather than relying on a specific visual hierarchy until the references converge. A single input block may translate into multiple output blocks. The cumulative text limit across all markdown blocks in one payload is 12,000 characters. A supplied block_id is ignored and not retained.
markdown_text Chat Arguments
The following current Web API methods accept a standard-Markdown markdown_text argument with a 12,000-character limit:
| Methods | Combination rules |
|---|---|
| chat.postMessage, chat.postEphemeral, chat.scheduleMessage, chat.update | Do not combine markdown_text with blocks or text; Slack returns markdown_text_conflict |
| chat.startStream, chat.appendStream, chat.stopStream | Accept Markdown during a stream; chat.stopStream can also append final blocks, rendered after streamed Markdown/chunks |
Streaming methods also accept chunks. In the current method references, a Markdown chunk is:
{ "type": "markdown_text", "text": "**Streaming** response" }
chunks can also carry task updates, plan updates, and block chunks. Consult the individual streaming method reference for the exact request fields and current limits. Link unfurling is disabled in streaming messages.
Work Objects
Work Objects do not use mrkdwn for their metadata string formatting:
{
"type": "string",
"value": "**Blocked** — see [runbook](https://example.com/runbook)",
"format": "markdown"
}
- A Work Object
fieldsorcustom_fieldsproperty of typestringcan setformat: "markdown". This is incompatible with that field'siconorlinkproperties. - Work Object comments can provide
{ "value": "...", "format": "markdown" }; comments may alternatively provide structured rich-text blocks. - A custom partial-access message can set
message_format: "markdown"for itscustom_message. - To reference a Work Object inside
rich_text, use a structuredwork_object_mentionelement (includingentity_id,app_id,text, andurl), not a made-up mrkdwn token. - Block Kit text objects embedded in composer or classic link unfurls still follow their own
mrkdwn/plain_textrules.
mrkdwn Syntax
| Format | Syntax | Notes |
|---|---|---|
| Bold | *bold* | Not **bold** |
| Italic | _italic_ | Not *italic* |
| Strikethrough | ~strikethrough~ | Not ~~strikethrough~~ |
| Inline code | `code` | Other formatting is disabled inside |
| Code block | Triple backticks around the text | No documented language-tag highlighting in mrkdwn |
| Block quote | >quoted text | Put > at the start of each quoted line |
| Link | <https://example.com\|display text> | Not [text](url) |
| Emoji | :emoji_name: or Unicode | Retrieved messages use colon form |
| Newline | \n in a string | Produces a line break |
| List-like text | - item / 1. item plus newlines | mrkdwn has no list syntax; these are text conventions |
Headings are not mrkdwn syntax. Use a header block, a standard-Markdown markdown block, or structured rich text as appropriate.
For complex combinations, true lists, or user-authored formatting, prefer structured rich_text instead of relying on undocumented marker nesting.
Links and Unfurls
https://example.com
<https://example.com>
<https://example.com|Display text>
<mailto:user@example.com|Email user>
Raw URLs in mrkdwn are normally auto-transformed into links. URLs containing spaces break parsing; remove or URL-encode the spaces. When messages are retrieved, Slack returns auto-transformed URLs in angle-bracket form, sometimes with an explicit label.
Slack normally unfurls links posted by users and apps, including media links in Block Kit blocks. For publishing methods that expose these parameters:
| Parameter | Controls |
|---|---|
| unfurl_links | Primarily text-based content |
| unfurl_media | Media such as images, video, and audio |
Set both to false to suppress all link previews. For messages containing LLM-generated URLs, disable unfurls by default with both unfurl_links: false and unfurl_media: false: Slack warns that its outbound preview request can complete prompt-injection data exfiltration. If previews are required, allow-list trusted external domains and reject or report URLs outside that list. Slack does not unfurl a manually labeled link when the label is a complete substring of the URL after removing the protocol (for example, <http://example.com|example.com>). Streaming messages do not unfurl links.
Custom app unfurls use the renderer of each receiving field. Composer preview elements can contain an object with type: "mrkdwn"; blocks within the unfurl use their normal text-object rules. Exception: chat.unfurl does not currently support rich-text blocks and may return invalid_blocks for an otherwise valid Block Kit payload containing a rich-text section element. A chat.unfurl user_auth_message supports simple Slack formatting, while user_auth_blocks supplies a Block Kit alternative. Work Object entity metadata remains a separate standard-Markdown case as described above.
Mentions and References
Manual, Stable Syntax
<@U0123ABC456> user mention
<#C0123ABC456> conversation link
<!subteam^SAZ94GDB8> user group mention
<!here> active channel members
<!channel> all channel members
<!everyone> everyone in #general (non-guest workspace members)
An app-published user mention notifies that user. An app-published user group mention notifies the group. Special mentions can notify many people and should be used sparingly.
Use IDs rather than names. User, conversation, and user-group names can change; their IDs are stable. A user who cannot access a referenced private channel sees an unclickable private channel label.
The current chat.postMessage reference says link_names finds and links user groups and no longer supports individual users. Do not depend on name auto-parsing for users, conversations, or special mentions. Use the explicit forms above.
Date Formatting
Slack localizes app-published dates to the timezone setting of the viewer's device, not the timezone preference in their Slack client.
<!date^{unix_timestamp}^{token_string}^{optional_link}|{fallback_text}>
| Token | Example behavior |
|---|---|
| {date_num} | 2014-02-18, with leading zeros |
| {date} | February 18th, 2014 |
| {date_short} | Feb 18, 2014 |
| {date_long} | Tuesday, February 18th, 2014 |
| {date_pretty} | {date}, but uses yesterday/today/tomorrow where appropriate |
| {date_short_pretty} | {date_short}, but uses yesterday/today/tomorrow |
| {date_long_pretty} | {date_long}, but uses yesterday/today/tomorrow |
| {time} | Viewer preference: 6:39 AM or 18:39 |
| {time_secs} | Viewer preference: 6:39:42 AM or 18:39:42 |
| {ago} | Human-readable elapsed time such as 3 minutes ago |
{date}, {date_short}, and {date_long} omit the year when the date is less than six months in the past or future. The optional third ^-separated value must be a fully qualified URL and makes the rendered date clickable.
Fallback text is required for older clients. Include a timezone in it because the fallback cannot be localized.
<!date^1392734382^Posted {date_num} {time_secs}|Posted 2014-02-18 6:39:42 AM PST>
<!date^1392734382^{date_short}^https://example.com/|Feb 18, 2014 PST>
Slack returns the original <!date...> string when messages are retrieved.
Escaping
Slack reserves exactly three characters for special parsing. When they are data rather than deliberate control syntax, encode them as:
| Character | Entity |
|---|---|
| & | & |
| < | < |
| > | > |
Do not HTML-encode the whole string: Slack only decodes these three documented entities. Escape & first, then < and >, to avoid re-encoding the ampersand you just introduced. JSON/string escaping is a separate concern.
Escape untrusted text before placing it in a mrkdwn-capable field. Otherwise it can introduce links, manual mention tokens, or date controls. Prefer plain_text when no formatting is needed.
Text Objects
[
{ "type": "mrkdwn", "text": "*bold* and _italic_", "verbatim": true },
{ "type": "plain_text", "text": "No formatting", "emoji": true }
]
The generic text object allows 1–3,000 characters. A containing block or element may impose a smaller limit; for example, section fields allow up to 10 objects of 2,000 characters each.
verbatimis valid only formrkdwn.emojiis valid only forplain_textand controls whether Slack escapes recognized emoji into colon format. It does not enable mrkdwn.
verbatim
| Value | Behavior |
|---|---|
| false (default) | Preprocesses plain content: raw URLs become links, conversation names may be linked, and certain mentions may be parsed |
| true | Skips that preprocessing, while still processing mrkdwn and explicit manual constructs such as <@U…> or <url\|label> |
Use verbatim: true when content contains raw @, #, or URLs that should not be automatically rewritten. It is not a way to neutralize deliberate angle-bracket control syntax; escape untrusted < and >.
Common Field Rules
This is a practical summary, not a substitute for the receiving component's reference:
| Context | Accepted text-object types |
|---|---|
| Section text and fields | mrkdwn or plain_text |
| Context text elements | mrkdwn or plain_text |
| Alert text (modal only, max 200) | mrkdwn or plain_text |
| Card/carousel-card title, subtitle, body, subtext | mrkdwn or plain_text; field-specific 150/200 limits apply |
| Checkbox/radio option text and description | mrkdwn or plain_text |
| Select, multi-select, and overflow option text/description | plain_text only |
| Header text, button text, placeholders, input labels/hints, view title/submit/close | plain_text only |
Slack adds Block Kit components over time. Verify the exact field reference before choosing a text type or limit.
Top-Level Message Text and Parsing
When a message has no blocks, top-level text is the rendered body and uses mrkdwn by default. When blocks are present, top-level text is a fallback rather than visible block content.
For chat.postMessage:
| Setting | Current documented effect |
|---|---|
| default parse | mrkdwn is applied; raw URLs are hyperlinked |
| parse: "none" | mrkdwn is still applied; raw URL hyperlinking is disabled |
| parse: "full" | mrkdwn formatting is ignored |
| mrkdwn: false | Disables mrkdwn processing for top-level text |
| link_names: true | Finds and links user groups; no individual-user linking |
Use explicit ID-based syntax and disable unwanted preprocessing. Slack's formatting guide recommends manual parsing because names can change and automatic parsing can turn third-party input into unintended notifications.
For best results, keep top-level text under 4,000 characters. Slack truncates messages over 40,000 characters. Blocks have their own limits.
Accessibility and Notification Fallbacks
Screen readers default to the message's top-level text and do not read interior blocks directly. With blocks, Slack documents two accessible approaches:
- Include every necessary piece of content in top-level
text. - Omit top-level
textand let Slack attempt to synthesize it from supported blocks.
Prefer an explicit, complete textual summary when notification and assistive-technology parity matters.
Notification behavior changed in July 2026:
- Desktop notifications extract text from supported blocks first, then fall back to
message.textif nothing can be extracted. - Mobile notifications exclusively use
message.text.
Do not assume a richly formatted block layout will produce a complete mobile notification or screen-reader experience.
Structured Rich Text
rich_text is the structured format produced by Slack's user composer and by rich_text_input. Slack's reference strongly prefers it for user-defined formatted text because it is more flexible than mrkdwn.
Use explicit elements such as rich_text_section, rich_text_list, rich_text_quote, and rich_text_preformatted, with nested elements/styles for text, links, emoji, users, channels, user groups, broadcasts, dates, and Work Object mentions. Do not put a mrkdwn string into a rich_text block and expect it to parse.
Legacy Secondary Attachments
Secondary attachments are legacy. Prefer Block Kit for new development. Legacy fields can be subject to reduced visibility or functionality.
mrkdwn_inis an array naming attachment fields to format as mrkdwn. The documented mrkdwn-capable legacy content istext,pretext, and field-objectvalues viafields.- A field-object
titlecannot contain markup and is escaped. fallbackis a plain-text summary for clients that do not show formatted attachment content.- Without attachment
blocks, one offallbackortextis required. - Attachment
textcollapses at 700+ characters or 5+ line breaks. - Slack allows no more than 20 attachments in a message.
{
"fallback": "Deployment completed",
"text": "Deployment *completed*",
"mrkdwn_in": ["text"]
}
Common Mistakes
| Mistake | Correction |
|---|---|
| **bold** or [text](url) in mrkdwn | Use *bold* and <url\|text> |
| *bold* in standard Markdown | It is italic; use **bold** |
| # Heading in mrkdwn | Use a header block or standard-Markdown surface |
| Passing syntax strings to rich_text | Build structured rich-text elements |
| Combining non-streaming markdown_text with text/blocks | Send one system; Slack returns markdown_text_conflict |
| Using link_names for user mentions | Use <@USER_ID>; link_names no longer links users |
| Treating verbatim: true as escaping | Explicit <...> controls still process; escape untrusted &<> |
| HTML-encoding every character | Encode only &, <, and > |
| Formatting Work Object fields with mrkdwn | Use standard Markdown plus format: "markdown" |
| Assuming block text covers every fallback | Supply deliberate top-level text when mobile notifications/a11y require it |
Reference Documentation
| File | Purpose | |---|---| | references/CHEATSHEET.md | Compact renderer choice, syntax, mentions, dates, parsing, and fallback rules |
Official Slack Sources
- Formatting message text
- Text object
- Markdown block
- Variable-sized header rollout — March 6, 2026
- Rich text block
- Alert block and Card block
- Option object
chat.postMessage,chat.postEphemeral,chat.scheduleMessage, andchat.updatechat.startStream,chat.appendStream, andchat.stopStream- Developing an agent — text streaming
- Implementing Work Objects and Work Object comments
- Work object mention element
- Unfurling links in messages
- Security best practices
chat.unfurl- Legacy secondary message attachments
- Desktop notification change — July 13, 2026