slackmark
v0.6.6
Published
Compile SlackMark (CommonMark and GFM plus Slack extensions) to Slack Block Kit JSON
Maintainers
Readme
slackmark
Slackmark turns Markdown into Slack Block Kit JSON. It supports CommonMark and
GFM, including task lists and tables, plus footnotes, Mermaid diagrams, math,
GitHub-style admonitions, and Slack entity tokens. It is especially useful for
agent and LLM output: models can write natural Markdown while Slackmark handles
Slack’s structure, capabilities, and limits. Compilation is rich_text-first,
capability-gated, and reports structured degradations.
Need a React preview? react-blockkit
renders any Block Kit JSON alongside slackmark.
Features
- Slackmark → Block Kit (
rich_text,header,table,data_table,markdown,data_visualization,container, …) - Capability profiles (
latest,conservative, dated presets) - Ordered fallback chains with
Degradationrecords (never silent) - Message budgets (50 blocks, table/markdown/chart caps)
- Mermaid
pie/xychart-beta→ native charts; other mermaid diagrams → configuredrenderersor preformatted fallback - Display math and non-mermaid code fences → configured
renderersor preformatted/code text - Reusable converters plus
convert()convenience for one-off calls
Quickstart
pnpm add slackmarkimport { convert } from "slackmark";
const { blocks, text, degradations, receipts } = await convert("# Hello **Slack**", {
timeoutMs: 5_000,
});Advanced (inject fakes / custom adapters):
import { createConverter } from "slackmark";
const converter = createConverter({
capabilities: "2026-03",
renderers: [myRenderer],
uploader: myUploader,
});
const result = await converter.convert(markdown);API
| Export | Role |
| --- | --- |
| convert / convertToMessages | Convenience functions (default wiring per call) |
| createConverter | Composition root |
| SlackMarkConverter | Stateless converter class |
| RemarkMarkdownParser | Default parser; rejects over-budget GFM table work with GfmTableParseComplexityError |
| NodeAdapter / Candidate | Extension seams |
| ImageRenderer / SlackUploader | Renderer and upload seams |
| validateBlocks | Limit validator |
Result shape: { blocks, text, degradations, receipts }. Receipts report external upload
effects separately from content fallbacks.
Direct RemarkMarkdownParser.parse() callers retain ownership of their unchanged source when
GfmTableParseComplexityError rejects a pathological table. convert() catches that structured
error and returns the source through its normal markdown/section fallback with a degradation.
Compat profiles
| Preset | Flags |
| --- | --- |
| latest (default) | all |
| conservative | all flags off |
| 2025-02 | + markdownBlock |
| 2025-08 | + tableBlock |
| 2026-03 | + preformattedLanguage, headerLevel |
| 2026-05 | + dataTable |
| 2026-06 | + dataVisualization, containerBlock |
Pass a preset name, a flags object, and/or capabilityOverrides.
Renderers & uploads
Prepend custom NodeAdapters via createConverter({ adapters }).
No renderer or uploader is enabled by default, so default conversion performs no network I/O. Mermaid fences consult the public renderers option after native pie / xychart-beta candidates. Display math and generic code fences consult the same ordered renderer list before their preformatted fallback. Each renderer's canRender method decides which normalized languages it accepts.
Hosted URL renderers compose credential-free HTTPS PNG URLs (no network I/O in core); they return null when Slack's 3,000-character image_url limit would be exceeded. Ordinary renderer, uploader, and capability-check failures remain scoped to one Markdown node; configuration and operation errors reject. Attempt failures are buffered while later renderers run: a later success emits no degradation, while an all-failed chain records structured details before text fallback.
Import first-party URL adapters from the dedicated subpath:
import { createConverter } from "slackmark";
import { KrokiRenderer, MermaidInkRenderer } from "slackmark/renderers";
const converter = createConverter({
renderers: [new MermaidInkRenderer()],
// or: renderers: [new KrokiRenderer()],
});MermaidInkRendereremits pako-state PNG URLs for mermaid fences with a non-transparent background by default.KrokiRendererhandles Mermaid and supported diagram fence languages such asd2,dot, andplantuml.CodeCogsRendererhandles display math andkatex,latex,math, ortexfences.QuickChartRendererhandleschart,chart.js,chartjs, andquickchartfences.
For local Mermaid and KaTeX PNGs in a Node bot, install slackmark-node and use
its resource-owning helper. See the
slackmark-node README and the
Rasterize content guide,
whose lifecycle example preserves both the primary error and a close error.
It reuses one lazy Chromium instance; the uploader lifetime and credentials stay
separate. slackmark-node requires Node 22.12+; core remains universal.
Hosted services receive the diagram source encoded in the URL when Slack fetches the image. Treat them as explicit data-egress opt-ins. Each adapter accepts baseUrl for a self-hosted deployment:
const privateRenderers = [
new KrokiRenderer({ baseUrl: "https://kroki.internal.example" }),
new MermaidInkRenderer({ baseUrl: "https://mermaid.internal.example" }),
];Bytes results use a SlackUploader. A missing uploader is deferred while later renderers can recover, then becomes a terminal ConfigurationError only if no renderer succeeds. Upload effects are returned separately in immutable receipts, including effects from failed attempts. The fetch implementation uses Slack's external upload flow:
import { createConverter } from "slackmark";
import { FetchSlackUploader } from "slackmark/slack";
const uploader = new FetchSlackUploader({
token: slackBotToken,
});
const converter = createConverter({
renderers: [localBytesRenderer],
uploader,
});Inject fetch, sleep, and clock functions for deterministic tests. The Slack token needs files:write. Optional files.info processing polling additionally needs files:read; explicit channelId sharing can create a separate file-share message.
Raw Slack entities
Markdown that already contains Slack mrkdwn tokens — <@U…>, <#C…>, <!here>,
<!subteam^S…>, <!date^…> — converts to native rich text elements (no resolvers
required). HTML-entity-escaped forms such as <@U123> convert the same way:
remark decodes character references before slackmark sees the text. To keep the
syntax literal, wrap it in a code span (`<@U123>`).
Optional remark plugins
remark-math and remark-frontmatter are dependencies; enable with enableMath / enableFrontmatter. Display math is parsed when enabled, then offered to configured renderers before preformatted fallback. Single-dollar inline math is a separate opt-in (enableInlineMath): agent prose is far likelier to contain $200/mo than $x$, and reading the span between two dollar signs as math deletes both signs. When it is opted into, inline math stays code-styled text.
License
MIT © Siraj
