While integrating DOCX generation into our document editor pipeline, I ran into one of the most confusing rendering issues I have seen so far.
A DOCX generated with multiple images displayed perfectly in Word, LibreOffice, and Google Docs. But inside the SuperDoc editor, every image slot showed the same image. Always the last one.
This post is not about a final fix yet. It is a breakdown of the debugging journey and what I discovered while tracing the root cause.
The Symptom
The generated report contained multiple property images arranged in a grid.
Expected inside editor:
- Image 1 → slot 1
- Image 2 → slot 2
- Image 3 → slot 3
Actual result inside SuperDoc:
- Slot 1 → last image
- Slot 2 → last image
- Slot 3 → last image
Every slot displayed the same image.
Strangely:
- Microsoft Word → correct
- LibreOffice → correct
- Google Docs → correct
- SuperDoc editor → wrong
So the DOCX itself was valid. The issue appeared editor-specific.
Environment
| Tool | Version |
|---|---|
| SuperDoc | 1.16.x |
| docx-templates | 4.15.0 |
| Node | 20.x |
Images inserted using docx-templates loops and loaded into editor via docxUrl.
First Check: Is DOCX Corrupt?
Initial assumption was that the DOCX generation might be broken.
So I verified:
- Opened in Word
- Opened in LibreOffice
- Converted to PDF
- Uploaded to Google Docs
Everything rendered correctly everywhere except inside the editor.
That ruled out basic DOCX corruption.
Deep Debugging Approach
Since DOCX files are just zip archives, I extracted and inspected raw XML:
word/document.xml
word/_rels/document.xml.rels
word/media/
Goal was to see how images were referenced internally.
Finding 1: Non-Standard Relationship IDs
Normally DOCX uses relationship IDs like:
rId1
rId2
rId3
But generated DOCX contained:
img2073076884
img99887766
`
Custom hash-based IDs.
Example:
xml
<Relationship Id="img2073076884"
Type=".../image"
Target="media/template_document.xml_img2073076884.jpg"/>
`
This is valid but not standard.
Test performed
Converted all relationship IDs to standard rId1, rId2, etc and updated references.
Result:
No change in editor behaviour.
All image slots still showed the last image.
So relationships were not the main cause.
Finding 2: Media Filenames
Generated filenames looked like:
`
template_document.xml_img2073076884.jpg
`
Instead of standard:
`
image1.jpg
image2.jpg
`
Renamed them to standard format and updated references.
Result:
Still broken in editor.
So filename convention was not the cause.
Finding 3: Images Inside Tables?
Another suspicion was that images inside table cells were being flattened or merged.
Checked raw XML structure.
Images were already in standalone paragraphs.
Not nested inside complex table drawing structures.
Result:
No structural issue found.
Finding 4: Base64 External References
As a further isolation step, I also converted images to base64-encoded external references to rule out any media file resolution issue inside the editor.
Result:
Still broken. All slots continued showing the last image.
At this point, the problem had to be in how the editor was interpreting drawing node identity, not how images were stored or referenced.
The Strongest Lead So Far (Unverified)
While inspecting drawing XML for each image, one pattern appeared:
`xml
<pic:cNvPr id="0" name="image1.jpg"/>
<pic:cNvPr id="0" name="image2.jpg"/>
<pic:cNvPr id="0" name="image3.jpg"/>
`
Every image had:
`
id="0"
`
According to the OOXML spec, this attribute is meant to be a unique identifier per drawing object within a document.
But docx-templates appears to be assigning id="0" to all generated images.
My working theory: SuperDoc uses ProseMirror under the hood for document rendering. If ProseMirror or SuperDoc's DOCX parser is using pic:cNvPr id to distinguish drawing nodes, duplicate IDs across all images could cause it to treat them as the same node, with the last image loaded winning every slot.
That said, this is a hypothesis, not a confirmed fix. The next step is to patch docx-templates output to inject unique sequential IDs, do a clean server restart, and verify the result properly. A failed test earlier in this session turned out to be against old compiled code because the server hadn't been restarted after a rebuild.
What Has Been Tried So Far
| Attempt | Result |
|---|---|
| Extract images from table | No change |
| Rename relationship IDs to rId1/rId2 | No change |
| Rename media filenames to image1.jpg | No change |
| Convert images to base64 external refs | No change |
| Inspect drawing XML | Found duplicate id="0" across all images |
| Patch to unique IDs | Not fully verified yet |
Current Status
Issue is still under investigation.
Leading hypothesis: duplicate pic:cNvPr id="0" across all images is causing the editor to collapse them into a single node.
Next step: inject unique IDs, do a full server restart, and run a clean verification.
Lessons From This Debugging Session
1. If it works in Word, it can still be wrong
Word tolerates many OOXML violations silently. Editors and converters are stricter. Never assume Word rendering equals correctness.
2. Always inspect DOCX internally
DOCX debugging becomes much faster once you treat it as a zip file. Unzip it, inspect the XML, search for patterns, verify structure.
3. Rendering engines behave differently
| Tool | Strictness |
|---|---|
| Word | Very forgiving |
| Google Docs | Moderate |
| LibreOffice | Strict |
| Editor engines | Very strict |
Editor-specific bugs often come from structural assumptions the spec makes but Word quietly ignores.
Why I'm Documenting This
This bug is still unresolved. But the debugging journey itself surfaced important learnings about DOCX internals, rendering engine strictness, and real-world document pipelines.
If you've encountered similar behaviour with DOCX image rendering, I'd genuinely like to hear what you found.
Top comments (1)
Hey Elizabeth - Caio here from SuperDoc. Thanks for writing this up, seriously. The debugging walkthrough was really well done, and the XML snippets you shared in the GitHub issue are what led us to the actual fix.
Turns out the duplicate
pic:cNvPr id="0"wasn't the cause - we don't use those IDs for anything. The culprit was<pic:spPr/>. docx-templates writes it as an empty self-closing tag, which is perfectly valid OOXML. But our importer assumed there'd always be something inside it, tried to look, and crashed. That crash got swallowed silently and took down image import for the whole document - every image, not just one.The fix was pretty straight forward: we just skip the lookup when there's nothing inside. github.com/superdoc-dev/superdoc/p...
Your closing line nailed it - different renderers handle things differently, and we want SuperDoc to match what Word does. This was a case where the file was valid and Word got it right - we just needed to catch up.
Thanks again for the post - it made our job easier and it's a great reference for anyone else running into DOCX weirdness ❤️