Tools
docmd CLI
Convert Microsoft Word documents to Markdown for retrieval indexes and human reading
#docmd CLI
docmd converts Microsoft Word documents to Markdown — for retrieval indexes, documentation
pipelines, or simply to read them somewhere other than Word. It is built on the PhoenixmlDb XSLT
engine, and its entire semantic layer is an XSLT 3.0 stylesheet you can print, edit and hand back.
#Installation
dotnet tool install -g Docmd.Cli
After installation, the docmd command is available globally.
#Usage
docmd <input.docx> [options]
#Options
|
Option |
Description |
|---|---|
|
|
Output directory (default: |
|
|
Map house styles to Markdown constructs (YAML) |
|
|
Run your own stylesheet instead of the built-in one |
|
|
Write the built-in stylesheet to stdout and exit |
|
|
Emit remote URLs for images while writing them locally |
|
|
Image folder name (default: |
|
|
Omit images entirely |
|
|
|
|
|
|
|
|
Show usage |
|
|
Show the version |
Accepts .docx, .docm, .dotx and .dotm. Word 97-2003 .doc is a different, binary
format — re-save it as .docx first.
#What it produces
docmd report.docx -o out/
---
title: Q3 Safety Review
author: A. Whitfield
created: '2026-04-11'
source: report.docx
sha256: 41ae9591…aefd
---
# Q3 Safety Review
The vent assembly is compliant with **AS9110** and *AS9100D*.
- First item
- Second item
- Nested child
YAML frontmatter carries the document's properties and a SHA-256 of the source file, so a converted corpus can be traced back to what produced it.
#Recovering structure, not extracting text
Nothing in OOXML says "this is a heading." Word offers several hints and real documents use all
of them, so docmd resolves headings through explicit outline level, the style's own level, the
style's inherited level, the style name, and finally direct formatting — recording which rule
fired so the decision is auditable rather than magic.
Lists are rebuilt from numId and ilvl, including nesting, because Word stores no nesting of
its own: every list item is a top-level paragraph carrying a number and a level.
That matters most for retrieval. Heading structure decides chunk boundaries — a missed heading merges two chunks, a false positive shatters a paragraph.
#It reports what it could not read
Every conversion is measured. docmd compares the words a reader can see in the .docx against
the words a Markdown parser recovers from its own output, and reports any that did not survive,
naming the structure responsible:
! 2 of 3754 words did not survive conversion (99.9 % kept).
! missing 'Here' near "the Discount List Click Here Click 'Add' to use"
! one of them sits inside <drawing>.
! one of them sits inside <txbxContent>.
Silent when nothing was lost, which on the reference corpus is most documents. A tool that warns about healthy documents is a tool whose warnings get ignored.
#Deterministic output
The same input, version and options produce byte-identical Markdown: LF endings everywhere, culture-invariant formatting, no timestamps, UTF-8 without a BOM. A corpus conversion is reviewable in a diff.
#Customising the output
Two escape hatches, in increasing order of effort.
A style map tells docmd what your template's own styles mean, in YAML:
CautionNote: { as: blockquote, prefix: "Caution: " }
ProcedureStep: { as: ordered-list-item }
PartNumber: { as: inline-code }
docmd report.docx --style-map house-styles.yaml
Keys match a w:styleId or the style name Word shows, whichever you have. Because a map fails
silently by nature — a misspelled id simply never matches — docmd reports both entries that
matched nothing and styles worth mapping.
When that is not enough, replace the transform:
docmd --print-stylesheet > mine.xslt
docmd report.docx --stylesheet mine.xslt
--print-stylesheet emits the stylesheet that really ran, so you edit what executed rather than
reconstructing it. It is one readable file of XSLT 3.0 and it is meant to be read.
#Source and limitations
github.com/phoenixmldb/docmd — Apache-2.0.
What is knowingly dropped, how large a document gets before conversion slows, and the measured
text-coverage figures are published in the repository's
docs/limitations.md.
Not yet implemented, each failing with a clear message rather than doing nothing quietly:
-r/--recursive, docmd audit, --review, --strict, --report and --revisions.