npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@heumsi/slackmd

v0.1.1

Published

Convert Markdown to Slack Block Kit JSON. Inspired by emailmd.

Readme

slackmd

Write Markdown, get Slack Block Kit.

npm version license made for Slack Block Kit

slackmd converts Markdown into Slack Block Kit JSON you can drop straight into chat.postMessage's blocks field.

It is inspired by emailmd — where emailmd turns Markdown into email-safe HTML, slackmd turns it into Slack-safe Block Kit. Write in a format you already know; get clean output for a fussy rendering surface. If you've used emailmd, slackmd should feel immediately familiar.


Why slackmd?

Slack has a native markdown block — so why a tool? Because it falls short exactly where rich messages need it:

| | Raw markdown block | slackmd | |---|---|---| | Images | Degraded to link text | Real image blocks | | Headings | All levels render identically | header blocks (visual hierarchy) | | Buttons / footers | Not possible | actions / context via directives | | Tables | Best-effort text | Native table blocks | | Output | Opaque string | Diffable, testable JSON | | Problems | Silent | Surfaced in a warnings[] array |

slackmd compiles Markdown to fully structured Block Kit by default — and can still emit a native markdown block when you want one (see Rendering engines).


Install

npm install @heumsi/slackmd

Requires Node.js ≥ 20. The package is pure ESM.


Quick start

Library

import { slackmd } from "@heumsi/slackmd";

const { blocks, warnings } = slackmd(`
# Deployment complete

The service is now running in **us-east-1**.

- Version: 2.3.1
- Duration: 45s
`);

await client.chat.postMessage({ channel: "#deploys", blocks });

slackmd is a pure function and never throws. Anything that can't map cleanly to Block Kit is downgraded gracefully and reported in warnings — see Warnings and limits.

CLI

slackmd notes.md                 # → { "blocks": [...] } on stdout
cat notes.md | slackmd --minify  # compact, reading from stdin

Full flags are in the CLI reference.


Worked example

Input

---
engine: rich_text
---

# Incident: DB Latency Spike

:::callout{level=warning}
Elevated latency detected on `db-primary`. Reads degraded, writes unaffected.
:::

## Timeline

- **14:02** — alert fired
- **14:08** — root cause identified (slow query, no index)
- **14:23** — hotfix deployed, latency normal

:::buttons
::button[View Runbook]{url=https://wiki.example.com/runbook style=primary}
::button[View Metrics]{url=https://metrics.example.com}
:::

:::context
Reported by PagerDuty · auto-posted by slackmd
:::

Output (abbreviated)

{
  "blocks": [
    { "type": "header", "text": { "type": "plain_text", "text": "Incident: DB Latency Spike", "emoji": true } },
    { "type": "rich_text", "elements": [{ "type": "rich_text_quote", "elements": [{ "type": "text", "text": "⚠️ " }, { "type": "text", "text": "Elevated latency detected on " }, { "type": "text", "text": "db-primary", "style": { "code": true } }, { "type": "text", "text": ". Reads degraded, writes unaffected." }] }] },
    { "type": "header", "text": { "type": "plain_text", "text": "Timeline", "emoji": true } },
    { "type": "rich_text", "elements": [{ "type": "rich_text_list", "style": "bullet", "indent": 0, "elements": ["..."] }] },
    { "type": "actions", "elements": [{ "type": "button", "text": { "type": "plain_text", "text": "View Runbook", "emoji": true }, "url": "https://wiki.example.com/runbook", "style": "primary" }, { "type": "button", "text": { "type": "plain_text", "text": "View Metrics", "emoji": true }, "url": "https://metrics.example.com" }] },
    { "type": "context", "elements": [{ "type": "mrkdwn", "text": "Reported by PagerDuty · auto-posted by slackmd" }] }
  ]
}

Mapping reference

slackmd targets the message surface (chat.postMessage) and emits only message-safe blocks.

| Markdown | Block Kit output | |---|---| | # H1, ## H2 | header block (plain_text, up to 150 chars) | | ### H3###### H6 | bold rich_text_section | | Paragraph, bold, italic, ~~strike~~, `code`, links | rich_text block | | :emoji_name: | emoji element inside rich_text | | - item, * item | rich_text_list (bullet) | | 1. item | rich_text_list (ordered) | | - nested | rich_text_list with indent | | - [ ] task, - [x] done | rich_text_list with ☐/☑ (or emoji) | | > blockquote | rich_text_quote | | ```lang … ``` | rich_text_preformatted | | --- | divider block | | ![alt](url) | image block | | GFM table | table block (column alignment preserved) | | Raw HTML | stripped (HTML_STRIPPED warning) | | :::buttons :::context :::fields :::callout | see Directives |


Rendering engines

Set the engine via the engine option (or --engine on the CLI).

rich_text (default)

Parses Markdown and emits fully structured Block Kit — header, rich_text, table, image, divider, and directive blocks. Full control over the output, broad compatibility across all bot types. Recommended.

markdown

Passes prose through Slack's native markdown block (one Slack-flavored Markdown string), intercepting only what that block can't do well: images become image blocks and directives become their own blocks.

Caveat: Slack documents the markdown block as tied to AI-feature apps, so its availability for a plain bot token isn't guaranteed. Prefer rich_text when in doubt.


Directives

Slack-native components that Markdown has no syntax for, written with remark-directive syntax. Directives work in both engines.

buttonsactions block

:::buttons
::button[View docs]{url=https://example.com style=primary}
::button[Cancel]{style=danger}
::button[Learn more]{url=https://example.com/learn value=learn}
:::

Each ::button supports url (link to open), style (primary or danger; omit for default), and value (payload for your action handler). An empty :::buttons is skipped with an EMPTY_BUTTONS warning.

contextcontext block

:::context
Posted by **DeployBot** · Region: us-east-1
:::

Small, muted text (with optional images) — ideal for footers, captions, and metadata.

fieldssection with two-column fields

:::fields
- Status: Active
- Region: us-east-1
- Owner: alice
:::

Each list item becomes a field, rendered in a two-column grid. An empty :::fields is skipped with an EMPTY_FIELDS warning.

calloutrich_text_quote with a leading emoji

:::callout{level=warning}
Avoid deploying during peak hours.
:::

| Level | Emoji | Use for | |---|---|---| | info | ℹ️ | Informational messages | | warning | ⚠️ | Caution / potential issue | | error | ⛔ | Errors / failures | | success | ✅ | Confirmations | | note (default) | 📝 | Notes / reminders |

Slack's alert block is modal-only, so callouts render as rich_text_quote blocks to display in messages.


Configuration

The same three options can be passed to the library, set in a document's frontmatter, or (on the CLI) given as flags. Precedence: caller option > frontmatter > default.

| Option | Type | Default | Description | |---|---|---|---| | engine | rich_text | markdown | rich_text | Rendering engine (see above) | | headerLevel | 16 | 2 | Highest heading level that becomes a header block; deeper headings become bold text | | taskList | unicode | emoji | unicode | Task checkbox style: / or :white_large_square:/:white_check_mark: |

Frontmatter — set defaults for a single document:

---
engine: rich_text
headerLevel: 2
taskList: unicode
---

# My document

Unknown keys or invalid values are ignored with a warning (UNKNOWN_FRONTMATTER_KEY / INVALID_FRONTMATTER_VALUE).

Headings — Markdown has six heading levels; Slack's header block has a single size and holds plain text only. So levels up to headerLevel become header blocks and the rest become bold rich_text. A heading longer than 150 characters or containing inline formatting also falls back to bold rich_textno text is lost (HEADER_TRUNCATED / HEADER_FALLBACK_BOLD).


CLI reference

slackmd [input] [options]

| Flag | Default | Description | |---|---|---| | [input] | stdin | Markdown file path; reads stdin if omitted | | -o, --out <file> | stdout | Write output to a file | | -e, --engine <engine> | rich_text | rich_text or markdown | | --header-level <n> | 2 | Max heading level for header blocks | | --task-list <style> | unicode | unicode or emoji task checkboxes | | --wrap <mode> | message | message{ "blocks": [...] }; blocks → bare array | | --minify | off | Compact JSON (default output is 2-space pretty-printed) |

# Pipe from stdin, emit a bare array for the Slack API `blocks` field
cat release.md | slackmd --wrap blocks

# markdown engine, written to a file
slackmd release.md --engine markdown -o payload.json

# H1–H3 as header blocks, emoji task checkboxes
slackmd notes.md --header-level 3 --task-list emoji

API reference

import { slackmd } from "@heumsi/slackmd";
import type {
  Block, Engine, SlackmdOptions, SlackmdResult, TaskListStyle, Warning,
} from "@heumsi/slackmd";

function slackmd(md: string, options?: SlackmdOptions): SlackmdResult;
interface SlackmdOptions {
  engine?: "rich_text" | "markdown"; // default: "rich_text"
  headerLevel?: number;              // 1–6, default: 2
  taskList?: "unicode" | "emoji";    // default: "unicode"
}

interface SlackmdResult {
  blocks: Block[];     // ready for chat.postMessage({ channel, blocks })
  warnings: Warning[]; // never throws — problems are reported here
}

interface Warning {
  code: string;
  message: string;
  line?: number;
}

Warnings and limits

slackmd never throws. Whatever can't be represented exactly in Block Kit is handled gracefully and reported in warnings (Array<{ code, message, line? }>). The limits below are Slack platform constraints, not slackmd restrictions.

| Code | Meaning | |---|---| | HEADER_TRUNCATED | Heading over 150 chars → rendered as bold rich_text (no text lost) | | HEADER_FALLBACK_BOLD | Heading with inline formatting → rendered as bold rich_text | | QUOTE_FLATTENED | Block content inside a blockquote was flattened to text | | LIST_ITEM_FLATTENED | Non-paragraph content in a list item was dropped | | TABLE_ROWS_TRUNCATED | Table exceeded 100 rows; extra rows dropped | | TABLE_COLS_TRUNCATED | Table exceeded 20 columns; extra cells dropped | | HTML_STRIPPED | Raw HTML was removed | | BLOCK_LIMIT_EXCEEDED | Over 50 blocks (Slack's per-message cap); output is unchanged — split across messages | | MARKDOWN_BLOCK_CHAR_LIMIT | markdown engine content exceeded 12,000 chars; split into multiple blocks | | EMPTY_BUTTONS | :::buttons had no ::button items; block skipped | | EMPTY_FIELDS | :::fields had no items; block skipped | | UNKNOWN_DIRECTIVE | Unrecognized :::directive name | | UNKNOWN_FRONTMATTER_KEY | Unrecognized frontmatter key | | INVALID_FRONTMATTER_VALUE | Known frontmatter key with an invalid value | | FRONTMATTER_PARSE_ERROR | Frontmatter is not valid YAML |


Roadmap

  • Card / carousel directives — native Slack card components
  • Mention resolution@username<@U123> via a user-lookup callback
  • Attachment-color callouts — colored sidebar via legacy attachments
  • 50-block auto-split — automatic message chunking past the block cap

Contributing

Issues and pull requests are welcome — please include tests for new behavior. See CONTRIBUTING.md for the dev setup, commit convention, and release process.

npm install
npm test
npm run build

License

MIT © heumsi — see LICENSE. Built in the spirit of emailmd.