docs/concepts/markdown-formatting.md
OpenClaw formats outbound Markdown by converting it into a shared intermediate representation (IR) before rendering channel-specific output. The IR keeps the source text intact while carrying style/link spans so chunking and rendering can stay consistent across channels.
<url|label>.<b>, <i>, <s>, <code>, <pre><code>, <a href>).text-style ranges; links become label (url) when label differs.Input Markdown:
Hello **world** — see [docs](https://docs.openclaw.ai).
IR (schematic):
{
"text": "Hello world — see docs.",
"styles": [{ "start": 6, "end": 11, "style": "bold" }],
"links": [{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }]
}
Markdown tables are not consistently supported across chat clients. Use
markdown.tables to control conversion per channel (and per account).
code: render tables as code blocks (default for most channels).bullets: convert each row into bullet points (default for Signal + WhatsApp).off: disable table parsing and conversion; raw table text passes through.Config keys:
channels:
discord:
markdown:
tables: code
accounts:
work:
markdown:
tables: off
If you need more on chunking behavior across channels, see Streaming + chunking.
[label](url) -> <url|label>; bare URLs remain bare. Autolink
is disabled during parse to avoid double-linking.[label](url) -> <a href="url">label</a> (HTML parse mode).[label](url) -> label (url) unless label matches the URL.Spoiler markers (||spoiler||) are parsed only for Signal, where they map to
SPOILER style ranges. Other channels treat them as plain text.
markdownToIR(...) helper with channel-appropriate
options (autolink, heading style, blockquote prefix).renderMarkdownWithMarkers(...) and a
style marker map (or Signal style ranges).chunkMarkdownIR(...) before rendering; render each chunk.<@U123>, <#C123>, <https://...>) must be
preserved; escape raw HTML safely.