chore: rename module to go.bigb.es/confluence-md-utilities
feat: add verify command, improve round-trip fidelity
feat: add ability for local install
Markdown to Confluence XML converter with bidirectional sync for self-hosted Confluence Server/Data Center.
Converts Markdown to Confluence storage format XML and back. Pull pages from Confluence, edit locally as Markdown, push changes back. Template-aware embedding preserves metadata tables, changelogs, inline comment markers, user references, and attachment images through round-trips.
go install go.bigb.es/confluence-md-utilities/cmd/mdcx@latest
Or build locally with just:
just install
Markdown to Confluence XML.
mdcx convert input.md -o output.xml
cat input.md | mdcx convert > output.xml
Convert Markdown and insert between marker comments in an existing Confluence document, preserving everything outside the markers (metadata table, TOC, changelog, etc.).
mdcx embed input.md --template template.xml -o output.xml
Template must contain marker comments:
<!-- MD_CONTENT_START -->
<!-- MD_CONTENT_END -->
Extract content between markers from a Confluence XML document and convert back to Markdown.
mdcx extract input.xml -o output.md
mdcx extract input.xml --raw # raw Confluence XML, no conversion
Fetch a page from Confluence and convert to Markdown.
mdcx pull "https://confluence.example.com/pages/viewpage.action?pageId=12345" -o page.md
mdcx pull "https://confluence.example.com/display/TEAM/Page+Title" -o page.md
mdcx pull "https://confluence.example.com/display/TEAM/Page+Title" --raw -o page.xml
Convert local Markdown and update a Confluence page.
# Replace entire page body
mdcx push "https://confluence.example.com/display/TEAM/Page" page.md
# Template mode: only replace content between markers, keep everything else
mdcx push "https://confluence.example.com/display/TEAM/Page" page.md --template
# With version message
mdcx push "https://confluence.example.com/display/TEAM/Page" page.md -m "Updated intro"
Pretty-print Confluence storage XML with syntax highlighting.
mdcx fmt page.xml
mdcx fmt page.xml --color=force # force colors even when piped
mdcx fmt page.xml --color=disabled # no colors
mdcx fmt page.xml -o formatted.xml # write to file (colors auto-disabled)
Colors: tags in blue/magenta/cyan by namespace, attributes in yellow, values in green, CDATA and comments in gray. Enabled by default when output is a terminal.
Block elements get their own lines with indentation. Short <li>, <h1>-<h6>, <th>, <td> stay on one line when content fits. Lines longer than 120 characters are wrapped at word boundaries (UTF-8 aware). CDATA content is preserved as-is.
pull and push require a Personal Access Token:
# Via environment variable
export CONFLUENCE_TOKEN="your-token-here"
mdcx pull "https://confluence.example.com/display/TEAM/RFC-42" -o rfc.md
# Via flag
mdcx pull --token "your-token" "https://confluence.example.com/display/TEAM/RFC-42" -o rfc.md
export CONFLUENCE_TOKEN="..."
# Pull
mdcx pull "https://confluence.example.com/display/TEAM/RFC-42" -o rfc.md
# Edit
vim rfc.md
# Push back, preserving template structure
mdcx push "https://confluence.example.com/display/TEAM/RFC-42" rfc.md --template -m "Updated requirements"
# Bash
source <(mdcx completion bash)
# Zsh (add to your .zshrc)
mdcx completion zsh > "${fpath[1]}/_mdcx"
# Fish
mdcx completion fish | source
| Markdown | Confluence XML |
|---|---|
# Heading |
<h1> ... <h6> |
**bold** |
<strong> |
*italic* |
<em> |
~~strike~~ |
<del> |
`code` |
<code> |
| Fenced code blocks | <ac:structured-macro ac:name="code"> with CDATA |
- item / 1. item |
<ul>/<ol> with <li> |
| Nested lists | Nested <ul>/<ol> inside <li> |
- [x] task |
<ac:task-list>/<ac:task> |
[text](url) |
<a href="..."> |
 |
<ac:image><ri:url .../> |
> blockquote |
<ac:structured-macro ac:name="info"> (info panel) |
--- |
<hr/> |
| GFM tables | <table> with <th>/<td> wrapped in <p> |
Confluence-specific elements that have no Markdown equivalent are preserved through round-trips using HTML spans:
| Confluence element | Markdown representation |
|---|---|
<ac:inline-comment-marker ac:ref="UUID"> |
<span data-inline-comment="UUID">text</span> |
<ac:link><ri:user ri:userkey="KEY"/></ac:link> |
<span data-user-key="KEY"/> |
<ac:image><ri:attachment ri:filename="F"/></ac:image> |
<span data-attachment="F"/> |
These are converted back to proper Confluence tags on push.
MIT