Skip to main content

Slack Markdown Formatting

Every Slack formatting syntax with copy-ready examples — and which of Slack's three formatting surfaces you are actually typing into, because they do not share a syntax.

Short answer

Slack's own format is mrkdwn, which is not Markdown: *bold*, _italic_, ~strike~, <url|text>, and no lists, headings or tables at all. But that is only one of three surfaces. The message composer accepts standard shortcuts like **bold** and converts them as you type, and since 2025 an app can send a Block Kit markdown block that takes real standard Markdown — headings, lists, tables, task lists and syntax-highlighted code included.

Where Markdown Works in Slack

Almost every “why is my Slack formatting broken” question is really this question. Slack has three places text gets formatted, and each one parses a different syntax.

1. The composer

Typing in Slack

WYSIWYG by default, with a formatting toolbar. It is the forgiving one: it accepts **bold** and [text](url) and converts them for you.

2. mrkdwn

The API message format

The text field and every mrkdwn text object in Block Kit. The strict one: single asterisks, angle-bracket links, no lists or headings.

3. markdown block

Standard Markdown, for apps

A Block Kit block that takes ordinary Markdown and converts it itself. The only surface with tables, task lists and highlighted code.

Slack formatting syntax by surface: message composer, mrkdwn, and the Block Kit markdown block
FeatureComposermrkdwn (API)markdown block
Bold**bold** or ⌘B*bold***bold**
Italic*italic* or ⌘I_italic_*italic*
Strikethrough~~strike~~ or ⌘⇧X~strike~~~strike~~
Underline⌘U — no syntaxNot supportedNot supported
Link[text](url) or ⌘⇧U<url|text>[text](url)
Inline code`code` or ⌘⇧C`code``code`
Code block``` or ⌘⌥⇧C``` — no highlighting```python — highlighted
Blockquote> quote or ⌘⇧9> quote> quote
Bulleted list* then space, or ⌘⇧8Not supported- item
Numbered list1. then space, or ⌘⇧7Not supported1. item
HeadingNot supportedNot supported# Heading
TableNot supportedNot supported| Col | Col |
Task listNot supportedNot supported- [ ] task
DividerNot supportedNot supported---
ImageUpload or pasteNot supported![alt](url) → link text
The trap: the composer is so forgiving that people conclude Slack understands standard Markdown, then paste the same text into a webhook payload and watch the asterisks show up literally. The composer converts as you type; a mrkdwn field does not.

Slack Text Formatting (mrkdwn)

This is the syntax that makes Slack different from every other Markdown. Because most renderers get it wrong, the right-hand panel below shows what Slack actually displays rather than a Markdown preview.

Bold — single asterisks

Slack uses one asterisk for bold. Two asterisks stay literal in mrkdwn.

mrkdwn
*bold text*
How it looks in Slack
bold text
Italic — single underscores

Underscores around the text. Asterisks mean bold in mrkdwn, not italic.

mrkdwn
_italic text_
How it looks in Slack
italic text
Strikethrough — single tildes

One tilde each side. The GitHub-style ~~double tilde~~ is not mrkdwn.

mrkdwn
~strikethrough~
How it looks in Slack
strikethrough
Bold + italic combined

Nest underscores inside asterisks. mrkdwn has no triple-marker shortcut.

mrkdwn
*_bold italic_*
How it looks in Slack
bold italic
Why underscores are the safer habit: _italic_ means italic in mrkdwn and in standard Markdown. The asterisk is the character that flips meaning between the two, so text written with underscores survives being moved between surfaces.

Standard Markdown in Slack: the markdown block

Slack introduced a Block Kit markdown block on 3 February 2025 and widened it on 6 March 2026 with tables, task lists, dividers, syntax-highlighted code blocks and variable-sized headers. It takes standard Markdown and does the conversion to Slack's internal format itself — so inside one, **bold** is correct and *bold* means italic, exactly backwards from mrkdwn.

Slack's own stated reason for it is AI: when an LLM hands your app Markdown, a markdown block lets Slack translate it instead of you writing a converter. If you are formatting model output for Slack, this is the block to reach for — see our Markdown for AI guide for the wider pattern.

A markdown block in a message payload
{
  "blocks": [
    {
      "type": "markdown",
      "text": "## Deploy finished\n\n- [x] tests green\n- [ ] changelog\n\n| env | status |\n| --- | ------ |\n| prod | green |"
    }
  ]
}
Emphasis follows CommonMark, not mrkdwn — two asterisks for bold
**bold**, *italic*, ~~strikethrough~~ and `inline code`
Preview

bold, italic, strikethrough and inline code

Headers work — but every level renders at the same size in Slack
# Header 1
## Header 2
### Header 3
Preview

Header 1

Header 2

Header 3

Ordered and unordered lists, which plain mrkdwn cannot do at all
- first item
- second item
  - nested item

1. first step
2. second step
Preview
  • first item
  • second item
    • nested item
  1. first step
  2. second step
Task lists render as real checkboxes (added March 2026)
- [x] shipped the release notes
- [ ] update the changelog
Preview
  • shipped the release notes
  • update the changelog
Tables — the single biggest thing mrkdwn has never been able to do
| Environment | Status |
| ----------- | ------ |
| production  | green  |
| staging     | red    |
Preview
Environment Status
production green
staging red
Three hyphens render a horizontal divider
Above the line

---

Below the line
Preview

Above the line


Below the line

Also available: markdown_text

chat.postMessage takes a markdown_text argument that accepts the same Markdown without building a block array. Slack's docs are explicit that it must not be combined with blocks or text, and caps it at 12,000 characters.

Two limits worth knowing

Every heading level renders at the same size, so # and ###### look identical. And ![alt](url) does not embed an image — Slack turns it into hyperlink text. Use an image block for a real image.

Mentions and Special Tokens

Mentions are IDs wrapped in angle brackets, not display names. The composer autocompletes @name into the right ID for you; an API message has to arrive with the ID already in it, or it is just text and nobody gets notified. Slack recommends the explicit form precisely because names change.

Mention a user

The member ID, never the display name — display names change, IDs do not.

mrkdwn
<@U012AB3CD>
How it looks in Slack
@username
Mention a channel

The channel ID. Slack fills in the current channel name when it renders.

mrkdwn
<#C123ABC456>
How it looks in Slack
#general
Mention a user group

The user-group ID after a caret. Renders as the group's handle.

mrkdwn
<!subteam^S0NHM1FH4>
How it looks in Slack
@team-name
Notify active members

Only pings people currently active in the channel.

mrkdwn
<!here>
How it looks in Slack
@here
Notify the whole channel

Pings every member. <!everyone> exists too, and only works in #general.

mrkdwn
<!channel>
How it looks in Slack
@channel
A timestamp in every reader's own timezone

Slack renders the token string locally and falls back to the text after the pipe.

mrkdwn
<!date^1392734382^{date_short} at {time}|Feb 18, 2014 at 6:39 AM>
How it looks in Slack
Feb 18, 2014 at 6:39 AM

Lists

Lists exist on two of Slack's three surfaces. In the composer, type * or 1. followed by a space and Slack builds a rich-text list as you go. In a markdown block, ordinary - item syntax renders properly. Plain mrkdwn has no list syntax at all — Slack's docs suggest mimicking one with hyphens and newlines, which is exactly as good as it sounds.

Unordered list — inside a markdown block, or typed in the composer
- first item
- second item
- third item
Preview
  • first item
  • second item
  • third item
Ordered list — the composer also renumbers automatically as you type
1. first step
2. second step
3. third step
Preview
  1. first step
  2. second step
  3. third step

Sending a real list from an app

A markdown block is the short route. For full control over indentation and numbering, build a rich_text block with rich_text_list elements.

What does not work

Posting { "text": "- one\n- two" } puts two hyphens on screen and nothing else. mrkdwn does not render bullets, indentation or numbering.

Headings

Headings are the thinnest part of Slack formatting. There is no heading button in the composer toolbar and no keyboard shortcut for one; mrkdwn leaves # as a literal character; and a markdown block does accept # through ###### but renders every level at the same size, so the hierarchy you write is not the hierarchy readers see.

For an app that needs one visually distinct title, the Block Kit header block is the reliable answer — it is a structural block rather than a piece of text formatting.

A header block above a body of mrkdwn
{
  "blocks": [
    {
      "type": "header",
      "text": { "type": "plain_text", "text": "Deploy finished" }
    },
    {
      "type": "section",
      "text": { "type": "mrkdwn", "text": "Shipped by <@U012AB3CD> at *09:41*." }
    }
  ]
}

Note the mixture: a header block takes plain_text only, so no formatting is parsed inside it, while the section below it is mrkdwn and so uses single asterisks. See Markdown headings for how the syntax behaves everywhere else.

Slack Markdown Tables

“Slack does not support tables” was true for a decade and is now out of date. There are three answers, depending on who is sending the message.

A pipe table inside a markdown block — supported since March 2026
| Service | Owner | Status |
| ------- | ----- | ------ |
| api     | Dana  | green  |
| web     | Ravi  | amber  |
Preview
Service Owner Status
api Dana green
web Ravi amber
Alignment colons work the same as GitHub-flavored Markdown
| Left | Center | Right |
| :--- | :----: | ----: |
| a    | b      | c     |
Preview
Left Center Right
a b c

From an app, quickly

Put a pipe table in a markdown block. Slack renders it as a real table, alignment colons included.

From an app, with control

The Block Kit table block, added 14 August 2025: up to 100 rows, 20 cells per row and 10,000 characters, with per-column settings.

Typing by hand

Still no table. Paste the rows into a code block so the monospace font holds the columns together, or share a canvas instead.

Need the pipe syntax itself? Our Markdown tables guide covers alignment and escaping, and the table generator builds one you can drop straight into a markdown block.

Quotes and Code Blocks

These are the constructs where all three surfaces agree, with one exception: a language identifier on a code fence is honoured inside a markdown block and silently discarded everywhere else.

Blockquote — one > at the start of the line
> This line is quoted.
Preview

This line is quoted.

Multi-line quote — repeat the > (Slack has no >>> shortcut like Discord)
> Line one of the quote.
> Line two continues it.
Preview

Line one of the quote.
Line two continues it.

Inline code — single backticks, identical everywhere
`inline code` stays monospaced
Preview

inline code stays monospaced

Code block — triple backticks. Plain mrkdwn ignores a language hint
```
function hello() {
  return "world";
}
```
Preview
function hello() {
  return "world";
}
Language hint — highlighted inside a markdown block, ignored in mrkdwn
```python
print("hello")
```
Preview
print("hello")
Inline code wins: Slack's docs state that text inside inline code takes no other formatting. Wrapping something in backticks is the simplest way to stop Slack from interpreting the asterisks and underscores in it.

The Composer: Shortcuts and the Markup Preference

By default the composer is WYSIWYG — you format with the toolbar or a shortcut and see the result as you type. Slack also ships a preference that turns typing into markup instead: click your profile picture, then Preferences → Advanced, and check “Format messages with markup” under Input options. Formatting is then applied only after the message is sent.

It has two side effects Slack documents and people rarely expect: automatic list formatting is turned off, and pasted messages arrive as plain text. That last one is a feature if you paste code and snippets all day.

Slack message formatting keyboard shortcuts
FormattingMacWindows / Linux
Bold⌘ BCtrl B
Italic⌘ ICtrl I
Underline⌘ UNot documented
Strikethrough⌘ ⇧ XCtrl ⇧ X
Inline code⌘ ⇧ CCtrl ⇧ C
Code block⌘ ⌥ ⇧ CCtrl Alt ⇧ C
Blockquote⌘ ⇧ 9Ctrl ⇧ 9
Bulleted list⌘ ⇧ 8Ctrl ⇧ 8
Ordered list⌘ ⇧ 7Ctrl ⇧ 7
Link⌘ ⇧ UCtrl ⇧ U
Apply markup formatting⌘ ⇧ FCtrl ⇧ F
Undo formatting⌘ ZCtrl Z
Underline is composer-only. ⌘U applies it, but there is no markup to type and no mrkdwn or markdown-block equivalent — Slack stores it as a rich-text style. Standard Markdown has the same gap, which our underline guide covers in full.

Escaping

Because mrkdwn builds links, mentions and date tokens out of angle brackets, three characters have to be escaped as HTML entities when an app sends mrkdwn. The composer handles this for you; a webhook payload does not.

Characters that must be escaped in Slack mrkdwn
CharacterSend insteadWhy
&&amp;Escaped in mrkdwn so Slack does not read it as the start of an entity.
<&lt;Otherwise Slack tries to parse it as a link, mention or date token.
>&gt;Otherwise it can close a mention or start a blockquote.

Inside a markdown block the rule is the ordinary Markdown one instead: a backslash before the character, so \*not italic\* keeps its asterisks. And to switch parsing off wholesale, set mrkdwn: false on a message, use a plain_text object inside a block, or set verbatim: true to keep formatting but stop Slack auto-linking names.

Common Mistakes

Every one of these comes from applying one surface's rules to another.

Does not work

**bold** in an API `text` field

Use instead

*bold*
Plain mrkdwn uses one asterisk. Two asterisks stay literal — unless you move the text into a markdown block, where **bold** is correct and *bold* means italic.

Does not work

~~strikethrough~~ in mrkdwn

Use instead

~strikethrough~
mrkdwn recognises a single tilde only. The double tilde works in the composer and in markdown blocks, and nowhere else.

Does not work

[click here](https://example.com) in a section block

Use instead

<https://example.com|click here>
A mrkdwn text object needs the angle-bracket form. Standard link syntax is for the composer and for markdown blocks.

Does not work

@alice from a bot

Use instead

<@U012AB3CD>
Typing @alice in the composer autocompletes to a real mention. An API message must already carry the member ID — plain text never becomes a ping.

Does not work

markdown_text alongside blocks or text

Use instead

markdown_text on its own
Slack's docs are explicit: markdown_text must not be combined with blocks or text in the same chat.postMessage call. Pick one.

Does not work

```python in a mrkdwn field, expecting colors

Use instead

```python inside a markdown block
A language hint is silently dropped by mrkdwn. Syntax-highlighted code blocks are a markdown-block feature added in March 2026.

Does not work

Rebuilding a table as aligned text because "Slack has no tables"

Use instead

A markdown block, or a Block Kit table block
That was true until 2025. Markdown blocks render pipe tables, and the table block gives an app structured rows with column alignment.

Slack Formatting Quick Reference

Every syntax on one screen, with the surface it belongs to.

Complete Slack formatting syntax reference
FormatSyntaxNotes
Bold*text* (mrkdwn) · **text** (composer, markdown block)The most common source of confusion on Slack.
Italic_text_ (mrkdwn) · *text* (composer, markdown block)Underscores are the safe choice in both.
Strikethrough~text~ (mrkdwn) · ~~text~~ (composer, markdown block)Single tilde is mrkdwn-only.
UnderlineNo syntax — ⌘U in the composerStored as a rich-text style, not as markup.
Link<url|text> (mrkdwn) · [text](url) (composer, markdown block)Both render identically once sent.
Autolink<https://example.com>Bare URLs also link themselves.
User mention<@U012AB3CD>Member ID, not display name.
Channel mention<#C123ABC456>Channel ID.
User group<!subteam^S0NHM1FH4>Group ID after a caret.
Broadcast<!here> · <!channel> · <!everyone>@everyone only works in #general.
Local timestamp<!date^unix^{date_short}|fallback>Rendered in each reader's timezone.
Inline code`code`Identical on every surface; suppresses other formatting inside.
Code block```code```Highlighting only inside a markdown block.
Blockquote> quoteRepeat > on each line; no >>> shortcut.
Bulleted list- itemComposer and markdown block only — not mrkdwn.
Ordered list1. itemComposer and markdown block only — not mrkdwn.
Heading# HeadingMarkdown block only; all levels render the same size.
Table| a | b |Markdown block, or the Block Kit table block.
Task list- [ ] taskMarkdown block only.
Divider---Markdown block, or the Block Kit divider block.
Image![alt](url)Becomes hyperlink text — use an image block or upload the file.
Emoji:tada:Shortcodes expand inline on every surface.
Escape&amp; &lt; &gt;Required in mrkdwn sent through the API.
Check before you ship: paste a message payload into Slack's Block Kit Builder to see it rendered before a bot sends it anywhere.

Frequently Asked Questions

Does Slack support Markdown?

Partly, and it depends on where you are typing. Slack's own message format is mrkdwn, which looks like Markdown but is not: a single asterisk means bold, underscores mean italic, and links use <url|text>. The composer accepts standard Markdown shortcuts and converts them as you type. And since February 2025 Slack apps can send a Block Kit markdown block, which does accept real standard Markdown — including headings, lists, tables and task lists.

Why does *text* show as bold in Slack but italic everywhere else?

Slack created mrkdwn before CommonMark stabilised and made different choices. In CommonMark a single asterisk means italic and a double asterisk means bold; mrkdwn uses a single asterisk for bold and an underscore for italic. This is the single biggest reason text pasted from GitHub or Discord looks wrong in a Slack API message.

How do I send a real Markdown message to Slack from a bot?

Use a markdown block, or the markdown_text argument on chat.postMessage. Both take standard Markdown and let Slack do the conversion. markdown_text cannot be combined with blocks or text in the same call, and the combined limit for markdown blocks in one payload is 12,000 characters.

Can Slack display a Markdown table?

Yes, since 2025 — which reverses years of advice to the contrary. A pipe table inside a Block Kit markdown block renders as a real table, and apps can also build one with the dedicated table block (up to 100 rows, 20 cells per row, 10,000 characters). What still has no table support is plain mrkdwn and the message composer, so a table typed by hand into Slack remains a code block with spaces for alignment.

What is the difference between the composer, mrkdwn and a markdown block?

They are three different formatting surfaces. The composer is the input box you type in — WYSIWYG by default, forgiving about syntax. Mrkdwn is Slack's stored message format, used by the text field and by mrkdwn text objects in Block Kit; it is the strict one. The markdown block is a newer Block Kit block that accepts standard Markdown and converts it for you. Which one you are using decides whether *bold* or **bold** is correct.

Does Slack support Markdown headings?

Only inside a markdown block, and all levels render at the same size, so # and ###### look identical. Plain mrkdwn has no heading syntax and leaves the # characters literal, and Slack's message composer has no heading option in its toolbar at all. Apps that want a distinct title should use a Block Kit header block.

Why don't my bullet lists work in Slack messages from the API?

Lists are not part of mrkdwn. Typing - or * in the composer creates a rich-text list, and a markdown block renders - item and 1. item correctly, but a plain mrkdwn text field leaves the hyphens as characters. From an app, send the list in a markdown block or build it with a rich_text block containing rich_text_list elements.

How do I link to a URL with custom text in Slack?

In mrkdwn, use <https://example.com|Click here> — angle brackets around the URL and a pipe before the display text. In the composer and in markdown blocks the standard [Click here](https://example.com) form works instead, and the composer also has ⌘⇧U for turning selected text into a link.

Can I get syntax highlighting in a Slack code block?

In a markdown block, yes — language-specific highlighted code blocks arrived in March 2026, so ```python is honoured. Everywhere else, no: plain mrkdwn and the composer render code blocks in a monospace font with no colouring and silently ignore the language hint.

How do I type Markdown in Slack instead of using the toolbar?

Turn on the markup preference: click your profile picture, choose Preferences, open Advanced, and check "Format messages with markup" under Input options. Your typing then stays plain until the message is sent, at which point Slack applies the formatting. It also turns off automatic list formatting and makes pasted text arrive plain.

Does Slack have underline formatting?

In the composer only, and only through the toolbar or ⌘U on Mac — there is no underline markup to type, and no mrkdwn or markdown-block equivalent. Standard Markdown has the same gap, which is why underlining is usually faked with bold or italic instead.

How do I mention a user from a Slack bot or webhook?

Use <@U012AB3CD> with the member's Slack ID, which you can look up with the users.list method. Channels use <#C123ABC456> and user groups use <!subteam^S0NHM1FH4>. Writing the display name as plain text never produces a notification.

How do I escape special characters in Slack API messages?

Replace the three characters Slack parses as control characters with HTML entities: &amp; for &, &lt; for < and &gt; for >. This applies to mrkdwn sent through the API — the composer escapes for you. Inside a markdown block, use a backslash instead, as in \*not italic\*.

How do I stop Slack formatting a message at all?

There are three levers. Set mrkdwn to false on a top-level message text to disable Slack markup parsing. Use a plain_text text object instead of a mrkdwn one inside a block. Or set verbatim to true on a mrkdwn text object, which keeps the formatting but stops Slack auto-linking channel names and user handles.