Convert standard Markdown into Slack Block Kit blocks.
- AST-based parsing — uses goldmark with GFM extensions for correct handling of nested/complex markdown
- Native Slack types — returns
[]slack.Blockfrom slack-go/slack, ready for direct API use - Rich output — headers, dividers, images, rich text (bold, italic, strikethrough, code), lists with nested indent, action buttons, blockquotes, code blocks, and tables
- GFM support — tables, strikethrough, task checkboxes, autolinks
- Message chunking —
ChunkBlockssplits output for Slack's 50-block-per-message limit
go get github.com/navidemad/md2slackRequires Go 1.25+.
import (
"github.com/navidemad/md2slack"
"github.com/slack-go/slack"
)
blocks, err := md2slack.Convert("# Welcome\n\nHello **world**.\n\n---\n\n")Output blocks (as JSON):
[
{
"type": "header",
"text": { "type": "plain_text", "text": "Welcome" }
},
{
"type": "rich_text",
"elements": [
{
"type": "rich_text_section",
"elements": [
{ "type": "text", "text": "Hello " },
{ "type": "text", "text": "world", "style": { "bold": true } },
{ "type": "text", "text": "." }
]
}
]
},
{ "type": "divider" },
{
"type": "image",
"image_url": "https://example.com/banner.png",
"alt_text": "banner"
}
]| Markdown construct | Block Kit block type |
|---|---|
# Heading through ###### |
header (plain_text, max 150 chars; falls back to bold mrkdwn section for links or overflow) |
---, ***, ___ |
divider |
 (standalone) |
image |
[text](url) (standalone) |
actions (button element) |
> quote |
rich_text (rich_text_quote) |
Fenced code blocks (```) |
rich_text (rich_text_preformatted) |
1. item / - item |
rich_text (rich_text_list with ordered/bullet style, nested indent) |
| GFM tables | table (native TableBlock with rich text cells, per-column alignment and wrapping) |
| Inline text with formatting | rich_text (rich_text_section with styled elements) |
Bold, italic, strikethrough, and inline code are represented as RichTextSectionTextElement entries with style flags rather than mrkdwn strings:
blocks, _ := md2slack.Convert("**bold** and _italic_ and ~~strike~~ and `code`")Links become RichTextSectionLinkElement with the URL and display text.
Lists are emitted as rich_text blocks with rich_text_list elements. Nested sub-lists use Slack's indent field:
blocks, _ := md2slack.Convert("1. First\n - Sub-item A\n - Sub-item B\n2. Second")[
{
"type": "rich_text",
"elements": [
{
"type": "rich_text_list",
"style": "ordered",
"indent": 0,
"elements": [
{
"type": "rich_text_section",
"elements": [{ "type": "text", "text": "First" }]
}
]
},
{
"type": "rich_text_list",
"style": "bullet",
"indent": 1,
"elements": [
{
"type": "rich_text_section",
"elements": [{ "type": "text", "text": "Sub-item A" }]
},
{
"type": "rich_text_section",
"elements": [{ "type": "text", "text": "Sub-item B" }]
}
]
},
{
"type": "rich_text_list",
"style": "ordered",
"indent": 0,
"elements": [
{
"type": "rich_text_section",
"elements": [{ "type": "text", "text": "Second" }]
}
]
}
]
}
]A paragraph containing only a markdown link becomes an actions block with a clickable button:
blocks, _ := md2slack.Convert("[Click here](https://example.com)")[
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "Click here" },
"url": "https://example.com"
}
]
}
]Slack limits messages to 50 blocks. Use ChunkBlocks to split. It also enforces at most one TableBlock per chunk, since Slack rejects messages containing multiple tables.
blocks, _ := md2slack.Convert(longMarkdown)
chunks := md2slack.ChunkBlocks(blocks, 50)
for _, chunk := range chunks {
// post each chunk as a separate message
}Parses a Markdown string and returns Slack Block Kit blocks. Returns nil, nil for empty input.
Splits a block slice into chunks of at most maxPerMessage. Defaults to 50 if maxPerMessage <= 0. Each chunk contains at most one TableBlock — when a table is encountered, the current chunk is finalized and a new chunk begins after the table.
The cmd/example directory contains a CLI tool that reads markdown from stdin or a file and prints the Block Kit JSON:
echo "## Hello\n\n**bold** and _italic_" | go run ./cmd/example/| Package | Purpose |
|---|---|
| github.com/yuin/goldmark | GFM-compliant Markdown parser (AST-based) |
| github.com/slack-go/slack | Canonical Slack Block Kit types |
See DEMO.md for a standalone script that posts converted Markdown to a Slack channel.
Fork the repository and open a pull request. Contributions are welcome!