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

mdast-util-from-slack-blocks

v0.1.0

Published

Convert Slack Block Kit rich_text blocks into mdast

Readme

mdast-util-from-slack-blocks

Convert Slack Block Kit rich_text blocks into an mdast Root.

Slack messages can contain structured rich text, while most markdown tooling expects mdast. This package bridges that gap so you can keep using the unified ecosystem.

Install

pnpm add mdast-util-from-slack-blocks

If you want Markdown output, add a serializer too:

pnpm add mdast-util-to-markdown mdast-util-gfm

Use

Minimal Slack blocks to Markdown example:

import { fromSlackBlocks, type SlackBlock } from 'mdast-util-from-slack-blocks'
import { gfmToMarkdown } from 'mdast-util-gfm'
import { toMarkdown } from 'mdast-util-to-markdown'

const blocks: SlackBlock[] = [
  {
    type: 'rich_text',
    elements: [
      {
        type: 'rich_text_section',
        elements: [
          { type: 'text', text: 'Ship ' },
          { type: 'text', text: 'today', style: { strike: true } }
        ]
      }
    ]
  }
]

const root = fromSlackBlocks(blocks)
const markdown = toMarkdown(root, { extensions: [gfmToMarkdown()] })

console.log(markdown.trim())
//=> Ship ~~today~~

Use resolvers and deterministic date options when you want stable output:

import { fromSlackBlocks, type SlackBlock } from 'mdast-util-from-slack-blocks'
import { gfmToMarkdown } from 'mdast-util-gfm'
import { toMarkdown } from 'mdast-util-to-markdown'

const blocks: SlackBlock[] = [
  {
    type: 'rich_text',
    elements: [
      {
        type: 'rich_text_section',
        elements: [
          { type: 'text', text: 'Hello ' },
          { type: 'user', user_id: 'U123' },
          { type: 'text', text: ', please review ' },
          { type: 'channel', channel_id: 'C456' },
          { type: 'text', text: ' by ' },
          { type: 'date', timestamp: 1_720_710_212, format: '{date_num} ({ago})' },
          { type: 'text', text: '. ' },
          { type: 'text', text: 'Old deadline', style: { strike: true } }
        ]
      }
    ]
  }
]

const root = fromSlackBlocks(blocks, {
  resolveUser: (userId) => (userId === 'U123' ? 'alice' : userId),
  resolveChannel: (channelId) => (channelId === 'C456' ? 'release-notes' : channelId),
  dateLocale: 'en-US',
  dateTimeZone: 'UTC',
  now: new Date('2024-07-11T15:03:32.000Z')
})

const markdown = toMarkdown(root, { extensions: [gfmToMarkdown()] })

console.log(markdown.trim())
//=> Hello @alice, please review #release-notes by 2024-07-11 (now). ~~Old deadline~~

mdast-util-gfm is only needed when you serialize mdast to Markdown and want GFM features such as strikethrough.

API

fromSlackBlocks(blocks, options?)

  • Input: SlackBlock[]
  • Output: mdast Root

All resolver and formatter options are synchronous.

| Option | What it affects | Default / fallback | | ------------------------------- | ----------------------------------------------------------- | ---------------------------------- | | resolveUser(userId) | Replaces @U123-style user mentions | Uses the raw Slack user ID | | resolveChannel(channelId) | Replaces #C123-style channel mentions | Uses the raw Slack channel ID | | resolveUsergroup(usergroupId) | Replaces @S123-style user group mentions | Uses the raw Slack user group ID | | resolveTeam(teamId) | Replaces @T123-style team mentions | Uses the raw Slack team ID | | renderDate(element, context) | Overrides built-in Slack date rendering | Uses the built-in renderer | | dateLocale | Locale for built-in date token rendering | Uses the runtime default locale | | dateTimeZone | Time zone for built-in date token rendering | Uses the runtime default time zone | | now | Reference time for built-in relative tokens such as {ago} | Uses new Date() | | unknownTypeHandling | Behavior for unknown Slack block/element types | 'ignore' |

Built-in date rendering uses format first, then template. If a template is unsupported, it falls back to fallback when present, otherwise to Date#toISOString().

Supported Slack Rich Text

Block-level mapping

| Slack type | mdast output | Notes | | ------------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | rich_text_section | One or more paragraph nodes | Double newlines split paragraphs; single newlines become hard breaks | | rich_text_list | list | style: 'ordered' sets ordered: true; ordered start comes from offset + 1, with offset taking precedence over legacy start | | rich_text_quote | blockquote | Section-only quotes become paragraph children inside the blockquote | | rich_text_preformatted | code | Content is flattened to plain text; language maps to code.lang |

Inline element support

Supported inline element types:

  • text
  • link
  • user
  • channel
  • usergroup
  • team
  • emoji
  • date
  • broadcast
  • color

Style and field handling

  • bold, italic, and strike map to mdast strong, emphasis, and delete.
  • code maps to mdast inlineCode and is exclusive over the other text styles.
  • underline, highlight, client_highlight, and unlink are preserved in node.data.slack.style because mdast has no native equivalent.
  • date.url wraps the rendered date text in an mdast link.
  • color is preserved as plain text such as #36c5f0.
  • rich_text_list.border, rich_text_preformatted.border, rich_text_quote.border, link.unsafe, emoji.unicode, and emoji.url are accepted input fields but do not change mdast output.

Important Behavior

  • Only Slack blocks with type: 'rich_text' are processed.
  • Non-rich_text blocks are skipped intentionally.
  • Empty rich text sections and whitespace-only paragraphs are omitted from the mdast tree.
  • Preformatted content ignores inline styling and serializes as plain code block text.
  • Unknown block or inline types respect unknownTypeHandling: 'ignore', 'warn', or 'throw'.

Compatibility

  • Node.js >=20
  • ESM-only package

Development

Tooling uses pnpm.

pnpm run check:all
pnpm run test:all
pnpm run ci:check

Run pnpm run build before publishing and before snapshot render scripts when source mapping logic, exports, or package output changes.

Snapshot Testing

Markdown snapshots live under test/snapshots/<case>/:

  • input.json: Slack message payload with a top-level blocks array
  • expected.md: committed, human-reviewed Markdown output
  • meta.json: source provenance and upstream example metadata

Useful commands:

pnpm run test:snapshots
pnpm run snapshot:render test/snapshots/<case>/input.json
pnpm run snapshot:render:all
pnpm run snapshot:import:slack-rich-text
pnpm run snapshot:refresh:slack-rich-text

Do not use Vitest snapshot auto-update for these fixtures. Update snapshots through the render scripts and review the generated Markdown diffs.

Imported snapshot fixtures currently come from slack-samples/bolt-js-examples, path block-kit/src/blocks/rich_text.js, commit 68028560836a97e90a16859f2d0017ec9d2f0eec.

Related

  • mdast-util-to-markdown
  • mdast-util-gfm
  • mdast-util-to-hast
  • mdast-util-from-adf