"Merge these Word files" sounds like a five-minute script. Then the merged document comes back with the wrong picture, headings in the wrong style and a numbered list that continues from nowhere.
I built two tiny test documents and merged them two ways to see exactly what breaks. Then I looked at why splitting a DOCX by page is a different kind of problem. Everything below is reproducible with python-docx, docxcompose and LibreOffice.
The test documents
-
A: US Letter landscape.
Heading 1is red, 26 pt. One picture (red), a 2-item numbered list, a table. -
B: A4 portrait.
Heading 1is blue, 14 pt. One picture (blue), a 2-item numbered list, a table.
Rendered alone, each looks exactly as described. Then I merged B into A two ways:
import copy
from docx import Document
from docxcompose.composer import Composer
# 1) "Naive": copy B's body elements into A
a, b = Document("A.docx"), Document("B.docx")
sect = a.element.body[-1] # A's final <w:sectPr>
for el in list(b.element.body):
if not el.tag.endswith("sectPr"):
sect.addprevious(copy.deepcopy(el))
a.save("naive.docx")
# 2) docxcompose
composer = Composer(Document("A.docx"))
composer.append(Document("B.docx"))
composer.save("composed.docx")
I converted both results to PDF with LibreOffice and compared them (LibreOffice 24.2 headless, python-docx 1.2.0, docxcompose 2.2.0; Word may differ in details).
What I found
| Check | Naive copy | docxcompose |
|---|---|---|
| B's picture | Wrong picture: shows A's red image | Correct (blue) |
| B's numbered list | Continues as "3. 4." | Restarts at "1. 2." |
B's Heading 1 style |
Takes A's style (red, 26 pt) | Takes A's style (red, 26 pt) |
| B's page size | A's (Letter landscape) | A's (Letter landscape) |
Two of these are bugs you'd notice. Two look like "features" but aren't what most people expect.
Why the wrong picture appears
A DOCX is a zip of XML parts. A picture in document.xml doesn't contain the image. It points to a relationship ID, and word/_rels/document.xml.rels maps that ID to a file:
A.docx document.xml: r:embed="rId9" rels: rId9 -> media/image1.png (red)
B.docx document.xml: r:embed="rId9" rels: rId9 -> media/image1.png (blue)
naive document.xml: r:embed="rId9" and r:embed="rId9" (both resolve to A's red image)
Both files happened to use rId9. Copy the XML without copying and renumbering the relationship, and B's picture resolves to A's. No error, no warning. Word opens the file happily. A merge that "preserves images" has to remap relationship IDs (and copy the media parts), not just copy elements.
Why styles and page size follow the first document
Paragraphs refer to styles by ID (Heading1), and the definitions live in styles.xml. When you move B's paragraphs into A's package, Heading1 resolves to A's definition. Page size, margins and orientation live in section properties (<w:sectPr>), and the naive script skipped B's.
Neither is wrong on its own. Merging into someone else's styles is a design decision. But if you merge a Letter-landscape document with an A4-portrait one, you should expect to check the layout of everything after the first document. Microsoft's own instructions for inserting a file into a Word document include the same advice in different words: insert the text "in a new section" so headers and footers apply only to those pages (Microsoft Support).
Why the numbered list restarts (or doesn't)
List numbering is also stored by ID, in numbering.xml. In my test, the naive copy attached B's list to A's numbering instance, so it continued from A's items. docxcompose created a separate numbering instance, so it restarted. Which one you want depends on the document, so verify rather than assume.
Splitting by page is a different problem
To merge, you manipulate XML. To split by page, you need to know where pages end, and a DOCX doesn't store that. Pagination is computed when the document is laid out, from fonts, margins, line spacing and the renderer.
Same file, same 70 paragraphs, only the base font size changed (LibreOffice rendering):
| Base font | Paragraphs in the file | Pages when rendered |
|---|---|---|
| 10 pt | 70 | 4 |
| 12 pt | 70 | 5 |
| 14 pt | 70 | 7 |
The XML of the generated file contained no page-break markers at all. (Word can write lastRenderedPageBreak hints when it saves, but they describe the last render on one machine. They aren't a guarantee.)
So "split every 3 pages" requires a renderer. The common pipeline is:
- Render the DOCX to PDF (LibreOffice headless:
soffice --headless --convert-to pdf file.docx). - Split the PDF by page index.
- Either hand back PDFs, or convert each part back to DOCX (for example with
pdf2docx).
Trade-offs worth knowing before you rely on it:
- Page boundaries come from that renderer's layout. Word, LibreOffice and Google Docs can paginate the same file differently, especially with missing fonts.
- Converting PDF back to DOCX reconstructs the layout, so results can differ from the original in places (text boxes, complex tables, tracked changes). Always check the output.
- If you only need page ranges for reading or review, splitting the PDF and stopping there is the most faithful option.
Checklist after any merge
- Open the result and scroll the whole document, not just page 1.
- Check images in every source document. Count them against the originals.
- Check numbered lists and headings after the first document.
- Check page size/orientation of later documents, headers and footers.
- If tracked changes or comments matter, test with a small sample first.
What online tools say about themselves
Limits and policies change, so I read each vendor's own page in November 2026 and note only what it states. "Not stated" means I didn't find it on that page, not that it doesn't exist.
| Tool | Limits stated | File handling stated | Source |
|---|---|---|---|
| GroupDocs Merger | "5 merges per day, 10 files in merge"; output as DOCX or PDF; no file-size limit stated | Uploaded files erased after 24 hours | page |
| Smallpdf Merge | No daily limit stated; the page lists DOC as a supported Word format and doesn't confirm DOCX | Files deleted after one hour; processing location not stated | page |
| iLoveMerge | "No limit to the number of Word documents"; no size limit stated | Meta description says "No uploads, browser-based processing"; the page doesn't explain how, and the upload prompt on the same page makes the claim worth verifying in DevTools | page |
| WORD.to | "1 conversion/hour, 1 file at a time"; free accounts up to 5 MB per file; the page says several files in one job need a paid plan | Files kept about 24 hours, then deleted | page |
| Wordize | FAQ: 3 files at a time as a guest (5 on free Basic), 5 MB per file as a guest (10 MB on Basic). The body text says up to 10 files, so the page contradicts itself | Source files deleted after the merge; output kept 24 hours | page |
| Word desktop (Microsoft 365, 2016–2024) | Insert → Object → Text from File; repeat for each file | Local; the instructions page doesn't say whether this applies to Word for the web | Microsoft Support |
Where the page contradicts itself, test with your own files. And if a tool says "browser-based," the two-minute DevTools Network check (watch for a large POST when you add a file) will tell you more than the marketing line.
I also run a free Word merge and split tool at ToolTiny. Merge accepts up to 20 DOCX files (20 MB each) with drag-and-drop ordering; split offers every page, page ranges such as 1-3, 5, 7-9, or every N pages, returned as a ZIP; files are deleted from the server after 10 minutes. Its split uses the render-to-PDF approach described above, so everything in the trade-offs section applies to it too. Use the checklist on its output as you would with any other tool.
Limits of this post
- The experiments use one pair of tiny documents, python-docx,
docxcomposeand LibreOffice. Word itself may behave differently in some details, and other libraries have different defaults. - Real documents add headers, footers, comments, tracked changes, fields and embedded objects, which I didn't test.
- Vendor limits are as stated on their pages on the date I read them.
If you have a merge that broke in a way not covered here, describe it in the comments. I'll try to reproduce it.

Top comments (0)