Blog / OOXML

Why Merging Word Documents Corrupts Them (and What the “Repair” Dialog Really Means)

July 7, 2026

If you’ve ever tried to merge Word documents in code — with python-docx, docxcompose, a naive zip-and-replace, or LibreOffice headless — you’ve probably met this dialog:

We found a problem with some content in ‘merged.docx’. Do you want us to try to recover as much as we can?

Sometimes the file recovers and looks fine. Sometimes headers vanish, numbered lists restart mid-document, or a heading suddenly inherits the wrong font. And sometimes it opens perfectly in LibreOffice and only breaks in Microsoft Word — or worse, only in Word for Mac.

Merging .docx files looks like it should be easy. The output that survives Word’s open-and-save cycle is the part nobody warns you about. Here’s what’s actually going on.

A .docx is not a document — it’s a package

A .docx file is a ZIP archive (the Open Packaging Conventions, or OPC) containing a tree of XML “parts”:

word/document.xml        ← the body text
word/styles.xml          ← style definitions (Heading1, Normal, …)
word/numbering.xml       ← list/numbering definitions
word/header1.xml, footer1.xml
word/media/image1.png
word/_rels/document.xml.rels   ← relationships: which part points to what
[Content_Types].xml      ← declares the type of every part
_rels/.rels

The body doesn’t contain its images, styles, or headers directly. It references them by ID. And that indirection is exactly where merging goes wrong: every reference is scoped to a single document. Concatenate two documents’ bodies and you’ve just mixed two independent ID namespaces together.

Here are the four collisions that produce most “corrupt” merged files.

1. Relationship ID (r:id) collisions — the classic corruptor

Inside document.xml, an image isn’t embedded — it’s referenced:

<a:blip r:embed="rId4"/>

rId4 is resolved through that part’s _rels file:

<Relationship Id="rId4" Type=".../image" Target="media/image1.png"/>

Every document numbers its relationships from rId1 independently. Document A’s rId4 might be an image; document B’s rId4 might be a hyperlink. Merge their bodies without remapping every relationship ID into a fresh namespace, and rId4 now resolves to the wrong target — or to nothing. A dangling relationship is precisely the kind of thing Word’s validator rejects on open. That’s your repair dialog.

2. Style ID collisions — the silent one

Both documents almost certainly define Heading1 and Normal — with different definitions (different fonts, sizes, spacing):

<!-- Document A -->
<w:style w:styleId="Heading1"><w:rPr><w:sz w:val="32"/></w:rPr></w:style>
<!-- Document B -->
<w:style w:styleId="Heading1"><w:rPr><w:sz w:val="28"/></w:rPr></w:style>

Naively concatenate the style parts and you either get duplicate styleIds (which Word may repair) or one definition silently wins — so document B’s headings inherit document A’s font. This is the “Heading1 from doc B inherits doc A’s font” bug. It doesn’t corrupt the file; it quietly wrecks the formatting.

3. Numbering (numId) collisions — why lists restart

Numbered and bulleted lists reference numbering definitions:

<w:numPr><w:numId w:val="3"/></w:numPr>

numId 3 maps to an abstractNumId in numbering.xml. Two documents both using numId 3 for different lists will, after a naive merge, share one definition — so the second document’s list restarts at 1, continues someone else’s numbering, or adopts the wrong bullet glyph.

4. Section properties (sectPr) — where headers and footers go to die

Headers, footers, page size, and margins live in section properties, which reference header/footer parts by relationship ID:

<w:sectPr>
  <w:headerReference w:type="default" r:id="rId7"/>
  <w:pgMar w:top="1440" w:bottom="1440" w:left="1440" w:right="1440"/>
</w:sectPr>

Most naive mergers keep only the first document’s section — so every header and footer after document 1 silently disappears. Preserving them means treating each merged document as its own section, carrying its own sectPr (with its own margins and header/footer references, all remapped through collision #1).

Why “it opens in LibreOffice but breaks in Word”

LibreOffice is famously forgiving — it will happily open a package that Microsoft Word rejects. So a merge that “works” in your test (opened in LibreOffice, or on Word for Windows) can still throw the repair dialog on Word for Mac, which validates references more strictly and on a different open-and-save path. If your only test is “does it open on my machine,” you will ship corruption to half your users.

Why the free libraries only get you 80%

docxcompose is genuinely good — it handles numbering, styles, and media better than most. python-docx was never designed to merge at all. But the last 20% — the reference remapping across every part, per-section header/footer preservation, media de-duplication by content, and the Word-Mac-specific validation edge cases — is where general-purpose libraries stop and your bug reports begin. There is no small fix; the Word format is roughly forty thousand pages of specification plus the parts Microsoft never documented.

What “doing it right” looks like (in shape, not in full)

You stop thinking of it as “concatenating documents” and start thinking of it as namespace reconciliation:

  • Remap every scoped identifier — relationship IDs, style IDs, numbering IDs, bookmark IDs — into a single merged namespace so nothing collides.
  • Register every appended part in [Content_Types].xml and the relevant _rels, so there are no dangling or unregistered parts for Word to reject.
  • Preserve each source document as its own section, carrying its own sectPr — margins, columns, and header/footer references intact.
  • De-duplicate embedded media by content, so merging ten copies of the same logo doesn’t produce a ten-megabyte file.
  • Validate the result the way Word does — not the way LibreOffice does.

Get all of that right and the file opens with no repair dialog, on Word for Mac and Windows, and survives the open-and-save round trip. Get any one of them wrong and you’re back to “We found a problem with some content.”

If you'd rather not maintain 40,000 pages of edge cases

mergedocx is a hosted API that does exactly this: POST a base template plus N source documents, get back one clean .docxthat opens cleanly in Word (Mac & Windows) — styles reconciled, headers and margins preserved per section, media de-duplicated. It stays a .docx: editable, which is the whole point for contracts, policy packets, and reports a human still needs to touch.