A DOCX editor must understand enough of Word's document model to edit and lay out a document. It must also support file content outside that model.
A .docx file is an Open Packaging Conventions (OPC) ZIP containing XML parts,
binary parts, relationships, and package metadata. Word can write content that
an editor does not need to render or edit: custom XML, VBA
projects, embedded objects, compatibility settings, fields, bookmarks, and
application-specific extensions.
An implementation can convert the document into a smaller representation, edit that representation, and generate a DOCX from it. This process removes anything outside the intermediate representation.
docx-editor uses a canonical representation for the parts that it must
understand. Everything else remains part of the original package. This
separation supports round-trip preservation.
The guarantee has a limited scope. Editing a document should not remove or reinterpret content that the editor did not modify. Saving does not reproduce the original bytes.
The package is not the document model
A DOCX file is not one XML document.
The input is an OPC package:
document.docx
├── [Content_Types].xml
├── _rels/.rels
├── word/
│ ├── document.xml
│ ├── styles.xml
│ ├── numbering.xml
│ ├── settings.xml
│ ├── media/
│ └── ...
└── ...Some of those parts need to be understood by the editor. Others do not.
The editor parses parts with an XML content type into its canonical tree, up to a bounded part count. It does not decode and reconstruct binary parts during a save. They remain package entries and pass through unchanged.
That gives the serializer two different jobs:
- Serialize the parts that the editor changed.
- Preserve everything else from the original package.
The canonical tree is a partial representation on purpose. In a
DOCX → JSON → DOCX architecture the intermediate format is everything that
survives a save.
The canonical tree only models what the editor needs
The core tree has typed nodes for constructs that layout and editing require, such as paragraphs, runs, and tables.
Unknown XML does not cause the parser to throw it away. It becomes a generic node that holds the original element, its attributes, and its children:
This design supports documents that contain unimplemented extensions. The parser preserves an unknown element without assigning unsupported semantics to the layout engine.
Unknown and invalid content require different handling.
If a known element appears somewhere the editor cannot safely interpret, treating it as a normal typed node can assign incorrect semantics. The parser therefore preserves some constructs as generic nodes.
The parser models supported content and preserves unsupported content.
Saving is not a full reconstruction
The same principle applies in the other direction.
A save does not take the current visual representation and rebuild a DOCX from scratch. The editor serializes the canonical tree for the parts it owns. Package entries that were never part of that editable representation stay available to the package writer.
This process preserves the following entries during an edit to another part:
- images and other binary media
- embedded fonts
- OLE objects
- VBA projects
- package relationships
- XML parts the editor does not modify
An edit to one part does not turn every other part into regenerated output.
IDs and references are part of the preservation problem
Content preservation also requires stable references.
DOCX contains many identifiers that connect otherwise separate pieces of the package: relationship IDs, bookmark names, style IDs, numbering definitions, field references, and others. An editor that regenerates these identifiers produces a document that looks correct immediately and contains broken references.
docx-editor preserves existing identifiers rather than renumbering them during
serialization. New relationship IDs are minted only for genuinely new content.
Paragraph IDs are one deliberate exception. They must satisfy Word's
uniqueness requirement, so an incoming w14:paraId that is missing, malformed,
or duplicated gets a replacement. The replacement is derived from the paragraph,
so the same input file produces the same ID every time. This is an explicit
normalization rule, not a side effect of rebuilding the document.
The DOM is not the source of truth
The browser adds another constraint.
The rendered page uses contenteditable, and the DOM cannot be the editor's
document model. Browsers can mutate editable markup in ways that support HTML
editing but have no DOCX equivalent. A browser mutation can produce a DOM
structure that the document tree cannot represent safely.
The editor therefore treats the DOM as a rendering surface. When the browser changes it, the editor compares the result with the structure it rendered and converts the difference into document operations. If the difference cannot be represented safely, the edit is rejected rather than silently discarded.
The browser never becomes the authoritative representation of the document.
A rejected mutation reaches the application through lastRejection in the
editor snapshot. The application can report an unsupported operation instead of
silently discarding the edit.
React and Vue are adapters, not separate editors
The implementation lives in @docx-editor.dev/core. React and Vue provide
bindings around the same engine. They do not maintain separate document models.
This separation also lets tests verify DOCX parsing, mutation handling, layout, and serialization independently of framework integration.
React and Vue use the same serialization implementation.
How the test suite verifies round-tripping
The test suite does not establish round-trip behavior from a few example files.
The test suite round-trips real documents and checks the result after parsing it again. Two levels of comparison are useful.
The first is the canonical tree. Fingerprinting the parsed representation detects changes introduced by parsing or serialization.
The second is a save-and-reopen cycle:
This catches a class of bugs that byte-level comparison cannot. XML serialization is allowed to normalize formatting and ordering, so the saved ZIP does not need to be byte-identical to the original. What matters is that reopening it produces the same document semantics for content the editor did not intentionally change.
Targeted tests cover fields, hyperlinks, tracked changes, and content controls. Browser tests also exercise the editing surface.
What "lossless" means here
It does not mean that saving a DOCX produces the same bytes. XML owned by the editor is parsed and serialized again, so its byte representation can change.
The guarantee is narrower and more useful:
Editing the document should not remove or reinterpret content that the editor did not modify.
Binary package payloads are preserved byte for byte. XML the editor owns is preserved structurally and semantically.
Some constructs are deliberately not editable. Text box story content renders but cannot be modified, and unsupported drawing geometry gets a placeholder that reserves its extent. Those are limits of the editing model, not reasons to discard the underlying DOCX content. Not editable is different from not preservable.
The practical test
Evaluate this behavior with a document that contains structures an HTML editor does not preserve.
Open a DOCX containing fields, bookmarks, custom XML, embedded objects, tracked changes, and other Word-specific structures. Change one sentence. Save it. Open both files in Word and compare everything around the edit.
The following demo uses the same engine. Its file picker accepts your document:
If unsupported content disappears, the preservation pipeline has a defect. Attach the file to a GitHub issue.
The implementation is open source in docx-editor. The pipeline is documented in Architecture, and the supported Word features are listed in Word fidelity.