chat
Version:
Unified chat abstraction for Slack, Teams, Google Chat, and Discord
301 lines (220 loc) • 7 kB
text/mdx
---
title: Markdown
description: AST builder functions and utilities for programmatic message formatting.
type: reference
---
The SDK uses [mdast](https://github.com/syntax-tree/mdast) (Markdown AST) as the canonical format for message formatting. Each adapter converts the AST to the platform's native format.
```typescript
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:
```typescript
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 — the required top-level wrapper for an AST.
```typescript
root([
paragraph([text("Hello, world!")]),
])
```
<TypeTable
type={{
children: {
description: 'Top-level content nodes (paragraphs, code blocks, blockquotes, lists).',
type: 'Content[]',
},
}}
/>
### paragraph
A paragraph block.
```typescript
paragraph([text("Hello "), strong([text("world")])])
```
### text
Plain text node.
```typescript
text("Hello, world!")
```
### strong
**Bold** text.
```typescript
strong([text("important")])
```
### emphasis
_Italic_ text.
```typescript
emphasis([text("emphasized")])
```
### strikethrough
~~Strikethrough~~ text.
```typescript
strikethrough([text("removed")])
```
### inlineCode
`Inline code` span.
```typescript
inlineCode("const x = 1")
```
### codeBlock
Fenced code block with optional language.
```typescript
codeBlock("const x = 1;", "typescript")
```
<TypeTable
type={{
value: {
description: 'Code content.',
type: 'string',
},
lang: {
description: 'Language identifier for syntax highlighting.',
type: 'string | undefined',
},
}}
/>
### link
Hyperlink.
```typescript
link("https://example.com", [text("click here")])
link("https://example.com", [text("click here")], "tooltip title")
```
<TypeTable
type={{
url: {
description: 'Link URL.',
type: 'string',
},
children: {
description: 'Link text content.',
type: 'Content[]',
},
title: {
description: 'Optional tooltip text.',
type: 'string | undefined',
},
}}
/>
### blockquote
Block quotation.
```typescript
blockquote([paragraph([text("Quoted text")])])
```
## Parsing and stringifying
### parseMarkdown
Parse a markdown string into an mdast AST.
```typescript
const ast = parseMarkdown("**Hello** world");
```
### stringifyMarkdown
Convert an mdast AST back to a markdown string.
```typescript
const md = stringifyMarkdown(ast); // "**Hello** world"
```
### toPlainText
Strip all formatting and return plain text.
```typescript
const plain = toPlainText(ast); // "Hello world"
```
### markdownToPlainText
Shorthand for parsing markdown and extracting plain text.
```typescript
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.
```typescript
const transformed = walkAst(ast, (node) => {
if (isStrongNode(node)) {
return emphasis(getNodeChildren(node));
}
return undefined;
});
```
### Type guards
Functions for checking node types:
| Guard | Matches |
|-------|---------|
| `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.
```typescript
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).
```typescript
import { parseMarkdown, tableToAscii, isTableNode } from "chat";
const ast = parseMarkdown("| Name | Role |\n|------|------|\n| Alice | Engineer |");
// Find the table node and convert it
```
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.
```typescript
import { tableElementToAscii } from "chat";
const ascii = tableElementToAscii(
["Name", "Age", "Role"],
[
["Alice", "30", "Engineer"],
["Bob", "25", "Designer"],
]
);
```
## Platform formatting
The SDK uses mdast as the canonical format and each adapter converts it to the platform's native syntax. You write standard markdown and the SDK handles the translation — but it helps to know how each platform renders common formatting.
| Feature | Slack | Teams | Google 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)` |
| Lists | Supported | Supported | Supported |
| Blockquotes | `>` | `>` | Simulated with `>` prefix |
| Tables | Native (markdown_text) | Native GFM | ASCII fallback |
| Mentions | `<@USER>` | `<at>name</at>` | `<users/{id}>` |
<Callout type="info">
Slack accepts standard markdown via the `markdown_text` field on `chat.postMessage` and friends, 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: "..." }`.
</Callout>
<Callout type="info">
You don't need to worry about these differences when using the SDK — the AST builders and `parseMarkdown` handle conversion automatically. This table is useful if you're working with `raw` platform payloads or debugging formatting issues.
</Callout>