I used to think paste was a solved problem.
You copy something, the browser reads the clipboard, and you insert HTML into the editor. Every rich text library has a paste handler. How hard could it be?
Then I watched a user paste a 30-page Word report into our editor. Tables broke. Lists renumbered themselves. Fonts disappeared. The whole document looked like it had been through a blender.
That's when I realized paste is one of those features that looks trivial from the outside and consumes an unreasonable amount of engineering time on the inside.
Here's the source content we'll use throughout this article — the same content copied from a Word document, rendered in a browser for clarity:
What the Browser Actually Gets
When you copy from Word, the clipboard contains several formats. The browser doesn't see all of them — it exposes what you can read through the Clipboard API:
const html = event.clipboardData.getData('text/html');
const text = event.clipboardData.getData('text/plain');
The text/html part is what matters. And it is not clean HTML.
Word writes its own dialect: XML namespaces for Office, mso-* CSS properties, conditional comments, VML fallbacks, and list numbering encoded as CSS rather than semantic tags.
Here's a simplified example of what actually lands in your paste handler:
<html xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:w="urn:schemas-microsoft-com:office:word">
<head>
<style>
p.MsoNormal { margin:0cm; font-size:12pt; font-family:"Times New Roman" }
</style>
</head>
<body>
<p class="MsoNormal">
<span style="mso-ansi-language:EN-US">
This is <b style="mso-bidi-font-weight:normal">bold</b> text.
</span>
</p>
</body>
</html>
The mso-* properties are meaningless to browsers. They only mean something to Microsoft Office. So if you pass the HTML through unchanged, the browser ignores most of the styling. If you strip everything, you lose the structure. Neither option works.
Why the Common Fixes Fail
Strip all styling
const div = document.createElement('div');
div.innerHTML = clipboardHtml;
const clean = div.textContent;
Result: plain text. Formatting gone. Tables gone. Images gone. Users hate it.
Insert raw HTML
editor.insertHTML(clipboardHtml);
Result: broken rendering, invisible conditional comments, ignored mso-* properties, and a lot of DOM nodes that don't behave the way users expect.
Run it through a sanitizer
Sanitizers are good at removing dangerous tags, but they don't understand Office markup. They might keep <table> but lose merged cells. They might keep <p> but destroy nested lists. The result looks okay until it isn't.
What Actually Works
The only approach I've found that preserves fidelity is to parse the Office HTML deliberately — source by source.
Word, Excel, WPS, and Google Docs all produce different clipboard HTML. Each has its own patterns:
-
Word uses
mso-*properties, conditional comments, and VML for shapes - Excel represents sheets as HTML tables with cell merges and number formats
- WPS follows Word's conventions but with its own extensions
- Google Docs produces cleaner HTML but with quirky nested spans and internal GUIDs
The parser needs to recognize where the content came from, then apply rules that understand that specific dialect. For example, Word list numbering isn't <ol> — it's a paragraph with mso-list properties and a hidden counter. You have to reconstruct the semantic list yourself.
<!-- Word gives you this -->
<p class="MsoListParagraph" style="mso-list:l0 level1 lfo1">
<span style="mso-list:Ignore">1.</span>
<span>First item</span>
</p>
<!-- You need to produce this -->
<ol>
<li>First item</li>
</ol>
Tables Are the Real Battle
If lists are annoying, tables are brutal.
Word tables carry:
- Horizontal cell merges (
gridSpan) - Vertical merges (
vMerge) - Background colors in
mso-*properties - Borders hidden in conditional comments
- Column widths in points
- Nested tables inside cells
Converting all of that into a clean HTML table while keeping the visual structure intact is where most of the complexity lives. In our paste parser, table handling alone took weeks to get right.
The Scale of the Problem
Our Office paste parser ended up being larger than I expected:
- Thousands of lines of TypeScript
- Separate handling for Word, Excel, WPS, and Google Docs
- Table logic that took weeks to get right
- Test documents covering real-world cases we collected from users
I thought paste would be a weekend feature. It became one of those projects that keeps expanding the deeper you go.
Conclusion
Paste fidelity is one of those things users expect to "just work" — and they notice immediately when it doesn't. Behind a simple Ctrl+V is a surprising amount of reverse engineering.
If you're building an application where users paste from Office documents, it's worth treating paste as a first-class feature rather than an afterthought.
If you want to see what Office-grade paste handling looks like, you can try it at www.cyteeditor.com/playground. Paste a complex Word document and see what survives.

Top comments (0)