OpenClawSkills
GitHub
Core Concepts β€’ TutorialHeader.readTime

Markdown Formatting

Markdown formatting pipeline for outbound channels.

OpenClaw formats outbound Markdown by converting it to a shared intermediate

representation (IR) before rendering channel-specific output. The IR preserves

source text intact while carrying style/link spans, so chunking and rendering can

stay consistent across channels.

Tutorial.step

Goals

- Consistency: One parse step, multiple renderers.

- Safe chunking: Split text before rendering, so inline formatting never

breaks across chunks.

- Channel matching: Map the same IR to Slack mrkdwn, Telegram HTML, and Signal

style spans without re-parsing Markdown.

Tutorial.step

Pipeline

1. Parse Markdown -> IR

- IR is plain text plus style spans (bold/italic/strikethrough/code/spoiler) and link spans.

- Offsets are UTF-16 code units, so Signal style spans match their API.

- Tables are only parsed when the channel opts into table conversion.

2. Chunk IR (format-first)

- Chunk IR text before rendering.

- Inline formatting never splits across chunk boundaries; spans are sliced per chunk.

3. Per-channel rendering

- ''Slack:'' mrkdwn tokens (bold/italic/strike/code), links as ''<url|label>''.

- ''Telegram:'' HTML tags (''<b>'', ''<i>'', ''<s>'', ''<code>'', ''<pre><code>'', ''<a href>'').

- ''Signal:'' Plain text + ''text-style'' spans; links become ''label (url)'' when labels differ.

Tutorial.step

IR Example

Input markdown:

Markdown
Hello **world** β€” see [docs ](https: //docs.openclaw.ai).

IR (schematic):

Json
{ "text": "Hello world β€” see docs.", "styles": [ { "start": 6, "end": 11, "style": "bold" } ], "links": [ { "start": 19, "end": 23, "href": "https://docs.openclaw.ai" } ] }
Tutorial.step

Where it's used

- Slack, Telegram, and Signal outbound adapters render from IR.

- Other channels (WhatsApp, iMessage, MS Teams, Discord) still use plain text or

their own formatting rules, with Markdown table conversion applied before

chunking when enabled.

Tutorial.step

Table handling

Markdown tables aren't consistently supported across chat clients. Use

''markdown.tables'' to control per-channel (and per-account) conversion.

- ''code'': Render tables as code blocks (default for most channels).

- ''bullets'': Convert each row to bullet points (default for Signal + WhatsApp).

- ''off'': Disable table parsing and conversion; raw table text passes through.

Config keys:

Yaml
channels: discord: markdown: tables: code accounts: work: markdown: tables: off
Tutorial.step

Chunking rules

- Chunk limits come from channel adapters/config and apply to IR text.

- Code fences are kept as single chunks with trailing newlines, so channels

render them correctly.

- List prefixes and blockquote prefixes are part of IR text, so chunking

doesn't split mid-prefix.

- Inline styles (bold/italic/strikethrough/inline code/spoiler) never split

across chunks; renderers reopen styles within each chunk.

If you need more info on cross-channel chunking behavior, see

''streaming + chunking''.

Tutorial.step

Link policy

- ''Slack:'' ''[label](url)'' -> ''<url|label>''; bare URLs stay bare. Autolinks

are disabled during parsing to avoid double-linking.

- ''Telegram:'' ''[label](url)'' -> ''<a href="url">label</a>'' (HTML parse mode).

- ''Signal:'' ''[label](url)'' -> ''label (url)'' unless label matches URL.

Tutorial.step

Spoilers

Spoiler tags (''||spoiler||'') are only parsed for Signal, where they map to

Spoiler style ranges. Other channels treat them as plain text.

Tutorial.step

How to add or update a channel formatter

1. ''Parse once:'' Use shared ''markdownToIR(...)'' helper with

channel-appropriate options (autolinks, heading styles, blockquote prefixes).

2. ''Render:'' Use ''renderMarkdownWithMarkers(...)'' and an implementing renderer

style token map (or Signal style ranges).

3. ''Chunk:'' Call ''chunkMarkdownIR(...)'' before rendering; render each chunk.

4. Wire adapter: Update channel outbound adapter to use new chunker

and renderer.

5. Test: Add or update formatting tests and outbound delivery tests (if

channel uses chunking).

Tutorial.step

Common pitfalls

- Angle bracket markers (''<@U123>'', ''<#C123>'', ''<https://...>'') must be

preserved; safely escape raw HTML.

- Telegram HTML needs text escaped outside of tags to avoid markup corruption.

- Signal style ranges depend on UTF-16 offsets; don't use code point offsets.

- Preserve trailing newlines for fenced code blocks so closing tags land on

their own lines.