How to Convert DOCX to Markdown: The Browser, Pandoc and mammoth Routes
4 September 2026 · 27 min read
You have a Word file and you need Markdown. Reasonable first move: open the .docx in a text editor and see what you are dealing with. What you get is a screenful of binary rubbish with the letters PK at the front and a few recognisable filenames buried in it. Nothing about that screen suggests a document.
That screen is the most useful thing you will see all day, because it tells you what the conversion actually is. A .docx is not a file with text in it. It is a zip archive containing a dozen XML files, and the words are in one of them while the meaning of the words is spread across the others. Converting it to Markdown means unzipping the archive, resolving those cross-references, and throwing away everything Markdown has no syntax for.
Which is why the same document converts differently in different tools, and why the failures are so specific. The headings arrive but the numbered list came out as plain paragraphs. The table arrived without its header row. The images are either missing, or present as a single line of base64 forty thousand characters long. The footnotes are simply not there, and nothing told you. Every one of those has a cause you can find in about two minutes once you know where to look.
This is the how-to: what is inside the file, three routes out of it, and then the part most guides skip — how to read the result and work out what it lost.
TL;DR
For one document, use a converter that runs in the browser: drop the .docx in, read the Markdown, no install and nothing uploaded. For more than one document, for images you need on disk, or for a file that has been through review, install Pandoc and use pandoc -f docx -t gfm --wrap=none --extract-media=./media. For conversion inside your own code, use mammoth to produce HTML and a separate HTML to Markdown step after it — that is what mammoth's own authors recommend. Then check three things in the output before you throw the .docx away: whether the numbered lists are still lists, where the images went, and whether the footnotes exist at all.
What a .docx actually is, and why a text editor shows nonsense
A .docx is a zip archive in the Office Open XML format, standardised as ECMA-376 and ISO/IEC 29500. Every zip file on earth begins with the two bytes PK, the initials of Phil Katz, who wrote the original format — so that is the first thing your text editor shows you, followed by compressed data it has no way to display.
Rename a copy to .zip, unzip it, and the document turns into a directory:
$ cp report.docx report-copy.zip
$ unzip -l report-copy.zip
[Content_Types].xml
_rels/.rels
word/document.xml
word/styles.xml
word/numbering.xml
word/settings.xml
word/fontTable.xml
word/footnotes.xml
word/media/image1.png
word/media/image2.jpeg
word/_rels/document.xml.rels
docProps/core.xml
docProps/app.xml
The exact list varies, and the variation is the interesting part. word/numbering.xml is only there if the document has ever had a list in it. word/footnotes.xml is only there if it has footnotes. word/media/ only exists if there are images. word/header1.xml appears if somebody set a running header. An archive missing one of those parts is missing the corresponding feature, and no converter can invent it.
On Windows, PowerShell will not expand an archive whose extension is not .zip, so copy it first:
Copy-Item report.docx report-copy.zip
Expand-Archive report-copy.zip -DestinationPath .\report-unzipped
word/document.xml is usually one enormous line, because Word has no reason to make it readable. Pipe it through a formatter before you try:
$ xmllint --format report-unzipped/word/document.xml | head -60
Now the important bit. In that XML, meaning is stored by reference. A heading is not tagged as a heading. It is a paragraph carrying a w:pStyle element that names a style, and the definition of that style — over in styles.xml — is what says it is Heading 1. A list item is a paragraph carrying a w:numPr element with a w:numId and a w:ilvl, and whether that is a bullet or a decimal number lives in numbering.xml. An image is an r:embed attribute holding a relationship id, and word/_rels/document.xml.rels is what turns that id into word/media/image1.png.
So a .docx to Markdown converter is a program that does four things in order: unzip the package, walk document.xml, resolve each element's references against the other parts, and serialise the result as Markdown. Every difference between tools is a difference in step three or step four. When step three cannot resolve something, the converter has no idea what it was looking at, and what you get is a plain paragraph.
| Part of the archive | What it holds | What breaks without it |
|---|---|---|
word/document.xml |
The paragraphs, runs and tables | Nothing converts at all |
word/styles.xml |
Named style definitions | Headings arrive as bold paragraphs |
word/numbering.xml |
List formats, levels and restarts | Numbered and bulleted lists arrive as paragraphs |
word/_rels/document.xml.rels |
Relationship ids to file paths | Images cannot be located |
word/media/ |
The image files themselves | Image references point at nothing |
word/footnotes.xml |
Footnote bodies | Footnote markers with no text, or no footnotes |
word/comments.xml |
Review comments | Comments dropped, usually silently |
Which route for which job
| Route | Best for | Install needed | What it does with images | Price |
|---|---|---|---|---|
| Browser converter | One document, now, without uploading it | None | Inlines them, or leaves references | Free |
| Pandoc | Batches, tracked changes, images on disk | Pandoc | --extract-media writes them to a folder |
Free, GPL |
| mammoth (Node or browser) | Conversion inside your own application | npm | Data URIs by default, or your own callback | Free, BSD-2-Clause |
| mammoth CLI | A one-off with images as files | npm | --output-dir writes them beside the HTML |
Free, BSD-2-Clause |
| MarkItDown | Feeding text to a pipeline, not a person | Python | Extracted where the format allows | Free, MIT |
| Word, Save as Web Page | A document other converters mangle | Word | Written to a folder beside the HTML | With Word |
| Google Docs export | A document already in Drive | None | Included in the download | Free with an account |
| Copy and paste | A few paragraphs, immediately | None | Lost | Free |
| LibreOffice, headless | Old .doc, .rtf and odd formats |
LibreOffice | Carried into the .docx it writes |
Free, MPL 2.0 |
| python-docx and your own writer | A house rule no converter implements | Python | Whatever you write | Free, MIT |
| unzip and read the XML | Diagnosing why a conversion failed | None | You are looking at them directly | Free |
Three of those rows are the routes almost everybody actually uses, and the rest of this article is mostly about them. If you want the routes compared as products rather than as procedures — pricing, licences, who each one suits — the full comparison of Word to Markdown tools covers the ones this page only lists.
The browser route: drop the file in, read the Markdown
A browser converter reads the .docx with JavaScript on your own machine. The archive is unzipped in the page, the XML is walked in the page, and the Markdown appears in the page. Signed out, no part of the file is sent anywhere, and that is checkable rather than a promise: open the network tab, convert, and watch nothing happen.
The procedure is four steps and there is nothing to configure.
- Open the conversion page.
- Drop the
.docxonto it, or pick it from the file dialog. - Read the Markdown that appears, and edit it in place if you need to.
- Download the
.md, or copy it out.
| Pros | Cons |
|---|---|
| No install, no terminal, no account | One document at a time, not a directory |
| Nothing uploaded when you are signed out | The browser does the work, so a very large file is limited by the machine |
| Headings, lists, tables, links, bold and italic come across | No option to extract images to a folder of your choosing |
| The result is editable before you take it away | Tracked changes resolve to accepted text; comments do not come across |
There is a size ceiling worth knowing about in advance, because it is the one thing that will stop you. On transformpipe, conversion itself is capped at 10 MB, and a document you keep in your history is capped at 4 MB, because the function that stores it refuses a larger request body. A .docx gets large for one reason — photographs — so if a file is over the line, the answer is usually to check what is in word/media/ rather than to assume the document is enormous.
Underneath, the browser route is generally mammoth plus an HTML to Markdown step, which is exactly the arrangement mammoth's own documentation recommends. That matters more than it sounds: it means the browser route and the mammoth route below have the same strengths and the same blind spots, and a document that converts badly in one will convert badly in the other.
Who it is for. Anybody with one document and a reason not to post it to a stranger's server — a contract, a patient note, an internal report, an unreleased plan. Also anybody who simply wants the Markdown in the next thirty seconds without learning a flag.
The Pandoc route: one command, and the four flags that matter
Pandoc is a command line document converter written in Haskell that reads and writes around forty formats. Its .docx reader is the most configurable one in existence, and it is the only route on this page with a documented answer for tracked changes.
The command in its shortest useful form:
$ pandoc -f docx -t gfm --wrap=none -o report.md report.docx
That is: read docx, write GitHub Flavored Markdown, do not reflow paragraphs, output to report.md. Leave --wrap=none off and Pandoc will hard-wrap your prose at 72 columns, which produces a diff-hostile file and is the first thing most people want to undo.
With the images extracted:
$ pandoc -f docx -t gfm --wrap=none \
--extract-media=./media \
-o report.md report.docx
And for a document that has been through review:
$ pandoc -f docx -t gfm --wrap=none \
--track-changes=all \
-o report.md report.docx
A whole directory, in bash:
$ for f in *.docx; do
pandoc -f docx -t gfm --wrap=none -o "${f%.docx}.md" "$f"
done
The same thing in PowerShell:
Get-ChildItem *.docx | ForEach-Object {
pandoc -f docx -t gfm --wrap=none -o "$($_.BaseName).md" $_.Name
}
| Flag | What it does | Why you want it |
|---|---|---|
-t gfm |
Selects GitHub Flavored Markdown as the output | Tables and strikethrough are GFM, not CommonMark. Pandoc's default dialect is its own extended Markdown, which is not the same thing |
--wrap=none |
Stops reflowing paragraphs at a column limit | One paragraph per line means readable diffs |
--extract-media=DIR |
Writes the embedded images out to a directory | Otherwise the images stay in the archive you are about to stop using |
--track-changes=accept|reject|all |
Decides what happens to insertions, deletions and comments | accept is the default and quietly discards the review; all keeps everything wrapped in spans |
--markdown-headings=atx |
Forces #-style headings |
Pandoc's own markdown writer uses underlined headings for the first two levels otherwise |
| Pros | Cons |
|---|---|
| Scriptable, so two hundred files cost the same effort as one | An install, and a terminal |
| The only documented control over tracked changes and comments | Its default output dialect is not GFM unless you ask |
| Images out to a folder with one flag | Custom Word styles need a mapping you write yourself |
Reads and writes .docx, so round trips are possible |
The manual is long and the flags are many |
Price: free, GPL licensed.
Who it is for. Anybody converting more than one file, anybody who needs the images as files, and anybody holding a document that has been through legal or editorial review. If a .docx has tracked changes in it, this is the only route on the page that will not silently resolve them for you.
The mammoth route: converting a .docx inside your own code
mammoth is a JavaScript library that converts .docx to HTML, with builds for Node and for the browser. A great many "Word to Markdown" tools turn out to be mammoth with a second step bolted on, and if you are writing your own converter it is the sensible foundation.
Its distinguishing idea is the style map. Instead of guessing what a paragraph is, mammoth matches Word's named styles to HTML elements, and the mapping is configuration you control:
const mammoth = require("mammoth");
const TurndownService = require("turndown");
const { value: html, messages } = await mammoth.convertToHtml(
{ path: "report.docx" },
{
styleMap: [
"p[style-name='Chapter Title'] => h1:fresh",
"p[style-name='Section Heading'] => h2:fresh",
"p[style-name='Intense Quote'] => blockquote:fresh",
],
}
);
const markdown = new TurndownService().turndown(html);
for (const message of messages) {
console.warn(message.message);
}
Two things in that snippet are the whole reason to use the library.
The first is styleMap. An organisation with house styles — "Chapter Title" rather than "Heading 1" — will get plain paragraphs from every other tool on this page, because there is no rule anywhere that says a style called "Chapter Title" is a heading. Here you write that rule. The :fresh suffix tells mammoth to start a new element rather than merge into the previous one, which is what you want for headings and what you do not want for a style that continues a paragraph.
The second is messages. Every mammoth result carries an array of warnings listing the styles it did not recognise and the elements it did not handle. This is the only machine-readable account of what a converter dropped that any route on this page provides. Print it, log it, show it to your users. An unrecognised-style warning is the exact moment to add a line to the style map.
mammoth's README marks its own Markdown writer as deprecated and recommends generating HTML and converting that to Markdown instead. Take the advice — HTML has an element for most things a .docx contains, Markdown does not, and going through HTML gives the second step something to work with. The choice of that second library is its own small decision, and the HTML to Markdown converters worth considering differ mainly in what they do with markup Markdown cannot express.
In the browser, the input is an ArrayBuffer rather than a path:
const buffer = await file.arrayBuffer();
const { value: html } = await mammoth.convertToHtml({ arrayBuffer: buffer });
And from the command line, for a one-off, the package ships a CLI that writes the images out as separate files rather than inlining them:
$ npx mammoth report.docx --output-dir=out
| Pros | Cons |
|---|---|
| Runs in Node and in the browser | Produces HTML; the Markdown step is yours |
| Style maps handle custom Word styles properly | Its own Markdown writer is deprecated by its authors |
Reports what it could not map, in messages |
JavaScript only |
| A CLI is included for one-off jobs | No page layout, because HTML has no page |
Price: free, BSD-2-Clause licensed.
Who it is for. Developers building conversion into an application, and anybody whose documents use house styles rather than Word's built-in ones. In the browser it is effectively the only real option.
Where the conversion fails, and what it costs
Everything above works. What follows is what happens anyway, because a .docx has hundreds of constructs and Markdown has about a dozen. The losses are structural, not bugs, and the useful question is which ones you are agreeing to.
Numbering only survives when numbering.xml resolves the list
This is the single most common complaint about .docx conversion, and it has a precise cause.
A numbered list in Word is a set of paragraphs, each carrying a w:numPr with a w:numId and a w:ilvl. That is all. The paragraph does not know it is numbered, does not know what number it is, and does not know whether it is a bullet or a decimal. All of that lives in numbering.xml, where a w:num element maps the w:numId to an abstract definition, and that definition holds one w:lvl per indent level with a w:numFmt saying bullet, decimal, lowerRoman and so on.
So a converter meeting a list paragraph has to follow two hops: w:numId to the numbering definition, then w:ilvl to the level inside it. If either hop fails — the part is absent, or it is present but does not contain the definition being referenced — the converter has nothing to go on. It does not know the paragraph was a list item at all. What it emits is an ordinary paragraph, and it emits it without complaint, because from its point of view nothing went wrong.
Read mammoth's source and the mechanism is visible directly: a level counts as ordered when its number format is anything other than bullet, and when the numbering part cannot be found the library falls back to an empty set of definitions. With an empty set, the lookup for a paragraph's numbering returns nothing, the paragraph stops matching the rule that would have made it a list item, and out it goes as prose.
That is why one document's lists convert perfectly and the next document's collapse. It is not the tool being inconsistent. One archive had a usable numbering part and the other did not — which happens to files assembled by scripts, exported from other applications, generated by report tools, or repaired by Word after a crash. Before you blame the converter, unzip the file and look:
$ unzip -l report-copy.zip | grep numbering
No word/numbering.xml in the listing means no route on this page will give you lists, and the fix is upstream: open the document in Word or LibreOffice, apply real list formatting, save, and convert the saved copy. And check the nesting on whatever does survive, because sub-levels flattening into the top level is a separate failure with its own causes — list indentation and line breaks misbehave in Markdown for reasons that have nothing to do with Word.
Images land as separate files, as base64, or nowhere
Markdown never contains an image. It contains a reference to one —  — and the file has to exist at that path when something renders the Markdown. A .docx, by contrast, contains the actual image bytes inside word/media/. Bridging that gap is a decision, and each route makes a different one.
| Route | What you get | What you then have to do |
|---|---|---|
Pandoc with --extract-media=./media |
Image files in ./media, references pointing at them |
Keep the folder next to the Markdown, and commit both |
| Pandoc without it | References into a path that does not exist on disk | Re-run with the flag |
| mammoth, default | <img src="data:image/png;base64,..."> in the HTML |
Decide whether you want one huge file or separate ones |
mammoth with a convertImage callback |
Whatever you write out | Write the files and return the src you want |
mammoth CLI with --output-dir |
Images as files beside the HTML | Convert the HTML to Markdown, paths intact |
| Copy and paste | Nothing | Save each image out of Word by hand |
The base64 case surprises people the most. A data URI is legal, self-contained, and renders correctly — and a single photograph becomes a line of Markdown tens of thousands of characters long, which makes the file unreadable in an editor, unreviewable in a diff, and slow in anything that highlights syntax. It is the right answer when the Markdown has to travel alone with no folder beside it, and the wrong answer in a repository.
mammoth's default is the data URI, and overriding it is a documented option rather than a workaround:
const options = {
convertImage: mammoth.images.imgElement(function (image) {
return image.read("base64").then(function (data) {
return { src: "data:" + image.contentType + ";base64," + data };
});
}),
};
That example reproduces the default; swap the body for code that writes the bytes to a file and returns a relative src, and you have images on disk with the paths you chose. Whichever route you take, the images are the part of the conversion most likely to be broken later rather than now, when the Markdown moves and the folder does not — what actually keeps an image reference working is worth reading before you commit a hundred converted files.
Headings that were never headings
If somebody built their headings by selecting a line, setting it to 18pt and pressing bold, there is no w:pStyle to resolve, and no converter can tell that line apart from an emphatic sentence. You will get **Chapter Two** as a paragraph, or plain text, depending on the tool.
This is not fixable in the converter, only upstream. Open the document, apply real heading styles from the styles gallery, save, convert again. If the document uses custom named styles instead, mammoth's styleMap is the answer and Pandoc needs a style mapping you write yourself. The cost of not fixing it is that your Markdown has no document structure at all — no table of contents, no anchors, no outline — and structure is most of what Markdown is for.
Tables that lose their header, or their shape
Markdown's table syntax is a grid of single cells, with a header row, no spanning, and no block content. A .docx table is a nested structure of rows and cells with merges, vertical alignment, nested tables and paragraphs inside cells.
A plain grid converts fine. Anything else degrades: a merged header cell becomes one cell and the columns shift, a cell containing a bulleted list becomes a cell containing the list's text run together, a nested table is flattened or dropped. Worse, the result usually looks plausible. The failure is not a mess on the page, it is a table that reads correctly and has the wrong data in the wrong column. Count the columns in the output against the columns in Word, on the widest table in the document, before you trust any of them — tables are the most common thing to break in either direction.
Header rows go missing for a specific reason worth knowing: Word marks a header row with a table-row property, and a converter that ignores it produces a table whose first row is an ordinary data row. Markdown requires a header row, so what you get is either a table with the first data row promoted into the header, or a table with an empty header and everything shifted down one.
Footnotes, comments and text boxes
Footnotes live in word/footnotes.xml and are referenced from the text by a w:footnoteReference. They have somewhere to land only in some flavours: footnotes are in neither the CommonMark nor the GFM specification, so they exist as extensions. Pandoc's own Markdown dialect has footnote syntax; a converter targeting strict CommonMark has to inline them, append them as ordinary paragraphs at the end, or drop them. Scroll to the bottom of the output and look before you assume.
Comments are a conversation attached to a range of text, and Markdown has no anchor to attach one to. Pandoc's manual states that accept and reject both ignore comments and only --track-changes=all includes them. mammoth leaves them out unless you add a comment-reference mapping yourself. Everything else drops them without saying so. The review thread is often the most valuable thing in a document and it is the first thing to go.
Text boxes and shapes are drawing objects, not part of the document flow. The text inside one can sit almost anywhere in the XML relative to where it appears on the page, and it commonly vanishes. This is the loss people find hardest to believe, because the pull quote was right there on screen. Search the output for a phrase you know was in a text box; if it is missing, it was never in the flow.
And then the things with no Markdown equivalent whatsoever: fonts, point sizes, colours, margins, page size, page breaks, headers, footers and page numbers. Not "poorly supported" — absent from the syntax. A tool that appears to keep them is emitting raw HTML with style attributes, which is a different document wearing a Markdown extension.
The checklist: what to read in the converted file
Do this once, on one representative document, before you convert two hundred. It takes about ten minutes and it is worth more than every comparison table including the one above, because your documents are not the same as anybody else's.
- Read the headings as a list.
grep -n "^#" report.mdgives you the document's outline in one screen. If it is short, headings became paragraphs — look for**Bold Line**on its own line, which is what a manually formatted heading turns into. - Find the lists. Search for lines starting with
1.,-or*. If the document had numbered procedures and the output has none, go and check forword/numbering.xmlbefore doing anything else. - Check list nesting. Sub-items should be indented under their parents. Flattened sub-levels are common and change the meaning of a procedure.
- Count the columns in the widest table. Compare against Word. Then check whether the header row is the header row, and not the first data row promoted into it.
- Look for the image references.
grep -n "!\[" report.mdlists them. Then confirm the files exist at those paths, or confirm the data URIs are there — a reference to a file that was never extracted renders as a broken image and nothing warns you. - Scroll to the bottom. Footnotes and endnotes either appear here, appear inline, or do not appear. Any of those may be acceptable; not knowing which one you got is not.
- Search for a phrase you know was in a text box, a caption or a callout. This is the test for the losses nothing reports.
- Search for a phrase you know was deleted during review. If it is present, tracked changes were kept as text. If a deleted phrase is gone and you needed the history, you converted with the wrong setting.
- Look at the top of the file. Word's field-based table of contents converts to whatever text was cached the last time Word updated it, complete with page numbers pointing at pages that no longer exist. Delete it and let your renderer build a new one.
- Open the Markdown in a renderer, not an editor. The editor shows you the syntax; the renderer shows you what a reader gets. They disagree more often than you would expect.
In PowerShell, the first, second and fifth of those are:
Select-String -Path report.md -Pattern '^#'
Select-String -Path report.md -Pattern '^\s*(\d+\.|[-*])\s'
Select-String -Path report.md -Pattern '!\['
| Symptom in the output | What actually happened | What to do |
|---|---|---|
| Headings are bold paragraphs | The document had no heading styles, or custom ones | Apply real styles in Word, or write a style map |
| Numbered lists are plain paragraphs | numbering.xml missing or unresolvable |
Check the archive; re-save from a word processor |
| Sub-items sit at the top level | Indent levels lost or flattened | Fix by hand; there is no flag for it |
| Table header row is a data row | The header-row property was ignored | Fix by hand, or convert via HTML instead |
| Columns do not line up | Merged or nested cells flattened | Restructure the table; Markdown cannot express merges |
| Broken image icons | References extracted, files not | Re-run with --extract-media or an output directory |
| One line of the file is 40,000 characters | Images inlined as data URIs | Switch to a route that writes files |
| Footnote text missing | Target flavour has no footnote syntax | Use a flavour that does, or accept inlining |
| Comments gone | Every route except one drops them | --track-changes=all, and keep the original |
| A pull quote is missing entirely | It was in a text box | Copy it across by hand |
How to choose a route
- Decide where the file is allowed to go before you pick a tool. A README can be uploaded to anything. A signed contract, an unreleased result or anything with a person's medical details in it cannot, and choosing a hosted converter for one of those is a disclosure rather than a conversion. Browser-side conversion keeps the file on the machine and you can verify that in the network tab.
- Count the documents, then count the clicks. One file does not justify installing a Haskell binary. Two hundred files do not justify a browser tab and a person clicking in it. The install is paid once; the clicking is paid every time, which flips the answer somewhere between five files and fifty.
- Establish whether the document has been reviewed. Tracked changes and comments are discarded by default almost everywhere. If the review matters,
--track-changes=allis the documented way to keep it, and if you are not using Pandoc then accept that it is gone rather than discovering it later. - Decide what you want to happen to the images before you convert, not after. Files in a folder, or base64 inside the Markdown. Both are defensible; neither is what you get by accident, and the accident is usually references pointing at nothing.
- Find out whether the document uses real styles. Open it in Word and click a heading: if the style box says Heading 1, every route will work. If it says Normal, no route will, and the fix is in the document rather than in the tool.
- Keep the
.docx. Everything in the section above is one-way. Archive the original where you can find it, because the day somebody asks what the deleted paragraph said is the day you learn the answer was only ever in the file you deleted.
Conclusion
Converting a .docx to Markdown is not translation, it is triage. If the document lives in Google Docs rather than on disk, that export has its own answer. The work is triage: unzip the archive, resolve what can be resolved, and accept the loss of everything Markdown has no syntax for. Knowing that the archive is where the answers live turns almost every mysterious failure into a two-minute check — no numbering.xml, no lists; no heading styles, no headings; no --extract-media, no images. For a single document, the shortest honest path is a converter that runs in your browser, which is what transformpipe's Word to Markdown conversion does, free, with no install and nothing uploaded when you are signed out. For a directory, for images on disk or for a reviewed document, install Pandoc. For conversion inside your own code, use mammoth, read its messages, and convert its HTML rather than its Markdown. Then run the checklist, because the losses that matter are the quiet ones — and an inventory of every one of them, with a verdict on which to mourn and which to be glad of is the thing to read before you decide any of it was worth keeping.
FAQ
How do I convert a .docx to Markdown without installing anything?
Use a converter that runs in the browser: it unzips and reads the archive with JavaScript on your own machine, so there is nothing to install and, signed out, nothing to upload. Confirm that last part by opening the network tab while it converts. The other no-install route is copy and paste, which carries headings, lists and links through the HTML clipboard but loses every image.
Why did my numbered lists come out as plain paragraphs?
Because the two-hop lookup into numbering.xml failed. A list paragraph in Word only carries a numbering id and an indent level; the format lives in that separate part of the archive, and if it is missing or references definitions it does not contain, the converter cannot tell the paragraph was ever a list item. Unzip the .docx and check for word/numbering.xml before blaming the tool.
What is the best command for converting docx to Markdown?
pandoc -f docx -t gfm --wrap=none --extract-media=./media -o out.md in.docx covers most cases: GitHub Flavored Markdown so tables survive, no paragraph reflowing so diffs stay readable, and images written to a folder rather than left in the archive. Add --track-changes=all if the document has been through review.
Can I convert a .doc rather than a .docx?
Not directly with any of these routes — the old binary .doc is a completely different format with no zip and no XML. Convert it first with LibreOffice in headless mode, soffice --headless --convert-to docx old.doc, then convert the .docx that produces. Expect the first step to be where the surprises are, since it is a full conversion of its own.
Will the images come across automatically?
No, because Markdown only ever references an image file rather than containing one. Pandoc's --extract-media writes them to a directory, mammoth inlines them as data URIs by default or hands them to a callback you write, and copy and paste loses them entirely. Check the images before you delete the source document.
Why do the headings work in one document and not another?
Because heading-ness is stored as a style reference, not as a property of the text. A document whose headings came from the styles gallery converts cleanly; a document whose headings are 18pt bold text has no style reference to resolve, so there is nothing for a converter to find. The tool is behaving identically in both cases — the documents are different.
Is going through HTML better than converting straight to Markdown?
Usually, yes, and it is what mammoth's authors recommend. HTML has an element for nearly everything a .docx contains, so the first step loses almost nothing, and the second step then makes one clear decision about what Markdown cannot express. Converting in a single hop means those decisions are taken silently, deep inside the reader, where you cannot see or change them.