Markdown

The SDK uses mdast (Markdown AST) as the canonical format for message formatting. Each adapter converts the AST to the platform's native format.

import {
  root, paragraph, text, strong, emphasis, strikethrough,
  inlineCode, codeBlock, link, blockquote,
  parseMarkdown, stringifyMarkdown, toPlainText, walkAst,
  tableToAscii, tableElementToAscii,
} from "chat";

Type re-exports

The chat package re-exports mdast's union and content types so adapters and downstream code can build exhaustively typed AST walkers without depending on mdast directly:

import type { Nodes, Root, Content } from "chat";

function render(node: Nodes): string {
  switch (node.type) {
    case "text": return node.value;
    case "strong": return node.children.map(render).join("");
    // ...
    default: throw new Error(`Unhandled: ${node satisfies never}`);
  }
}

Adapters use this pattern to make the type checker reject the build when a new mdast node type is introduced upstream.

Node builders

root

Root node. Every AST needs one as its top-level wrapper.

root([
  paragraph([text("Hello, world!")]),
])

Prop

Type

paragraph

A paragraph block.

paragraph([text("Hello "), strong([text("world")])])

text

Plain text node.

text("Hello, world!")

strong

Bold text.

strong([text("important")])

emphasis

Italic text.

emphasis([text("emphasized")])

strikethrough

Strikethrough text.

strikethrough([text("removed")])

inlineCode

Inline code span.

inlineCode("const x = 1")

codeBlock

Fenced code block with optional language.

codeBlock("const x = 1;", "typescript")

Prop

Type

Hyperlink.

link("https://example.com", [text("click here")])
link("https://example.com", [text("click here")], "tooltip title")

Prop

Type

blockquote

Block quotation.

blockquote([paragraph([text("Quoted text")])])

Parsing and stringifying

parseMarkdown

Parse a markdown string into an mdast AST.

const ast = parseMarkdown("**Hello** world");

stringifyMarkdown

Convert an mdast AST back to a markdown string.

const md = stringifyMarkdown(ast); // "**Hello** world"

toPlainText

Strip all formatting and return plain text.

const plain = toPlainText(ast); // "Hello world"

markdownToPlainText

Shorthand for parsing markdown and extracting plain text.

const plain = markdownToPlainText("**Hello** world"); // "Hello world"

AST utilities

walkAst

Transform an AST by visiting each node. Return a new value to replace the node, or undefined to keep it unchanged.

const transformed = walkAst(ast, (node) => {
  if (isStrongNode(node)) {
    return emphasis(getNodeChildren(node));
  }
  return undefined;
});

Type guards

Functions for checking node types:

GuardMatches
isTextNode(node)Plain text
isParagraphNode(node)Paragraph
isStrongNode(node)Bold
isEmphasisNode(node)Italic
isDeleteNode(node)Strikethrough
isInlineCodeNode(node)Inline code
isCodeNode(node)Code block
isLinkNode(node)Link
isBlockquoteNode(node)Blockquote
isListNode(node)List
isListItemNode(node)List item
isTableNode(node)Table
isTableRowNode(node)Table row
isTableCellNode(node)Table cell

getNodeChildren / getNodeValue

Safely access node properties without type narrowing.

const children = getNodeChildren(node); // Content[] | undefined
const value = getNodeValue(node);       // string | undefined

Table utilities

tableToAscii

Render an mdast Table node as a padded ASCII table string. Used by adapters that lack native table support (Google Chat, Discord, Telegram).

import { parseMarkdown, tableToAscii, isTableNode } from "chat";

const ast = parseMarkdown("| Name | Role |\n|------|------|\n| Alice | Engineer |");
const table = ast.children.find(isTableNode);

if (table) {
  console.log(tableToAscii(table));
}

Output:

Name  | Role
------|---------
Alice | Engineer

tableElementToAscii

Render a table from headers and string row arrays as a padded ASCII table. Used for card TableElement fallback rendering.

import { tableElementToAscii } from "chat";

const ascii = tableElementToAscii(
  ["Name", "Age", "Role"],
  [
    ["Alice", "30", "Engineer"],
    ["Bob", "25", "Designer"],
  ]
);

Platform formatting

You write standard markdown, and each adapter converts it to the platform's native syntax. This table shows how each platform renders common formatting.

FeatureSlackTeamsGoogle Chat
Bold**text****text***text*
Italic_text__text__text_
Strikethrough~~text~~~~text~~~text~
Code`code``code``code`
Code blocks`````````
Links[text](url)[text](url)[text](url)
ListsSupportedSupportedSupported
Blockquotes>>Simulated with > prefix
TablesNative (markdown_text)Native GFMASCII fallback
Mentions<@USER><at>name</at><users/{id}>

Slack accepts standard markdown via the markdown_text field on chat.postMessage and related methods, so the SDK passes markdown through directly. Incoming Slack messages still arrive as legacy mrkdwn (*bold*, <url|text>) and are parsed transparently. If you need to send mrkdwn yourself, use { raw: "..." }.

The AST builders and parseMarkdown handle these conversions for you. The table is mainly useful when you work with raw platform payloads or debug formatting issues.

Read more