
Total Views
59
Read Time
13 min read
Updated On
26.10.2026
Introduction
How to Export Rich Text Editor Content to PDF and Word in 2026
Step-by-step guide to export rich text editor content to PDF and Word (.docx) using Puppeteer and html-to-docx, with code, print CSS and a pre-ship checklist.
TL;DR
Export editor HTML to PDF with Puppeteer and to Word with @turbodocx/html-to-docx. Sanitize first, add print CSS, skip paid plugins.

Content
How to Export Rich Text Editor Content to PDF and Word in 2026
Quick answer: To export rich text editor content to PDF and Word, take the editor's HTML output, sanitize it, and send it through two converters: a headless browser such as Puppeteer for PDF (page.pdf()) and an HTML-to-DOCX library such as @turbodocx/html-to-docx for Word. Run both on the server for consistent, selectable-text files.
That's the whole pipeline in three sentences. The rest of this guide is about the parts that break in production: fonts that silently swap, tables that split across pages, images that come out blank, and the export button that works on your laptop but times out on a 40-page report.
We build Eddyter, a rich text editor, and export is one of the first things teams ask about after they've wired the editor into their app. So this is the approach we recommend in 2026, with working code you can drop into a Node or Next.js backend.
Why exporting rich text to PDF and Word is harder than it looks
Your editor speaks HTML. PDF and Word don't.
A PDF is a fixed-layout format: every glyph has an exact position on a page. A .docx file is a zip of XML (OOXML) with its own idea of paragraphs, runs, styles, and sections. HTML has none of those concepts. It has a scrolling box and CSS. Every export path is a translation step, and translation loses things.
Here's what usually gets lost:
- Page breaks. The browser never had pages. A heading can land as the last line of page 3 with its paragraph on page 4.
- Fonts. If the server rendering your PDF doesn't have your web font, it falls back to something else, and nobody notices until a customer does.
- Images. Relative URLs, signed S3 links that expired, or images behind auth all come out as empty boxes.
- Custom blocks. Embeds, mentions, callouts, and math equations only look right because of your app's CSS and JavaScript. A converter sees bare tags.
Keep that list in mind. Every decision below is about one of these four.
Step 1: Get clean HTML out of your editor
Everything starts with the HTML your editor produces. If that HTML is messy (inline styles everywhere, empty spans, editor-specific class names), both converters will turn the mess into a messier file.
In Eddyter, the onChange prop hands you the current HTML string, debounced about 300 ms after the user stops typing:
Store that HTML (we compared formats in HTML vs JSON vs Markdown for rich text storage), and export from the stored copy, not from whatever is on screen. Exporting from the database means the file always matches what was saved. It also lets you generate exports later from a background job, without the user's browser open.
If you're on another editor, the idea is identical. Tiptap has editor.getHTML(), Quill has quill.root.innerHTML (or getSemanticHTML() in v2), and Lexical-based editors use $generateHtmlFromNodes.
Sanitize before you convert
This step gets skipped constantly, and it's the one that matters most for security. The HTML came from a user. When you feed it to a headless browser on your server, you're rendering untrusted markup on your infrastructure.
A crafted <img src="http://169.254.169.254/..."> can make your PDF renderer request your cloud metadata endpoint. A <script> tag can run inside Chromium. So:
- Run the HTML through DOMPurify (or
isomorphic-dompurifyon Node) and strip scripts, iframes, and event handlers. - Convert images to base64 data URIs before rendering, so the converter never needs network access.
- Turn off JavaScript in the headless page and block outbound requests.
Add one more rule on top: never pass a user-supplied URL to page.goto(). Always use page.setContent() with the HTML string. Those habits close the most common hole in DIY export features.
Step 2: Export rich text editor content to PDF
You have three realistic options. One of them is the right default for most apps.
Method | Where it runs | Text selectable? | Best for |
|---|---|---|---|
| User's browser | Yes | Internal tools, quick wins, zero backend |
html2pdf.js / jsPDF + html2canvas | User's browser | Often no (image-based) | Short, visual one-pagers |
Puppeteer / Playwright | Your server | Yes | Customer-facing exports, reports, contracts |
Option A: Server-side PDF with Puppeteer (recommended)
Headless Chromium renders your HTML with the same engine your users see, then prints it to PDF with real, selectable, searchable text (see the Puppeteer page.pdf() docs). This is what we'd pick for anything a customer downloads.
The wrap() function is where the quality comes from. It puts your content inside a full HTML document with a print stylesheet:
Those few CSS rules fix most of the complaints you'll get. break-after: avoid on headings stops orphaned headings at the bottom of a page. break-inside: avoid on tables and images stops a chart from being sliced in half.
What goes wrong: fonts. Your Docker image probably doesn't ship with Inter or Noto. Install the fonts in the image (or embed them with @font-face and a base64 source) and test with non-Latin text. Hindi, Arabic, and CJK characters will render as empty boxes on a bare Linux container.
Also goes wrong: cold starts. Launching Chromium per request is slow and memory-hungry. On serverless platforms, use a slim Chromium build such as @sparticuz/chromium, or move export to a queue worker and email or notify the user when the file is ready.
Option B: The browser's print dialog
Underrated. If your users are internal staff, a "Print / Save as PDF" button that calls window.print() with a good @media print stylesheet gets you most of the result for a fraction of the work. The text stays selectable, and you need no server.
The catch is control. You can't set the filename, users can change margins and headers in the dialog, and the output varies slightly between Chrome, Safari, and Firefox.
Option C: html2pdf.js (use with care)
html2pdf.js and the jsPDF + html2canvas combo take a screenshot of your content and place it inside a PDF. It works in the browser with no backend, which is why so many tutorials recommend it.
But the result is usually a picture of text. Users can't search it, can't copy from it, and screen readers can't read it. Long documents also get blurry or sliced mid-line at page boundaries. We'd only use it for short, visual documents like a certificate or a one-page summary.
Step 3: Export rich text editor content to Word (.docx)
Word export is where most teams underestimate the work. A PDF only has to look right. A .docx file has to look right and stay editable, which means your HTML has to map onto real Word headings, lists, and tables, not just visually similar text.
Option A: @turbodocx/html-to-docx (recommended for JavaScript apps)
@turbodocx/html-to-docx is an actively maintained fork of the original html-to-docx package. It takes an HTML string and returns a Buffer in Node or a Blob in the browser.
Three settings worth knowing. Margins are in TWIP (twentieths of a point), so 1440 is one inch. The default font is Times New Roman, which is almost never what you want, so set font explicitly. And pageNumber only works when footer is true, which trips up nearly everyone on the first try.
What goes wrong: CSS. Word doesn't understand flexbox, grid, or most of your stylesheet. The converter reads semantic tags (h1–h6, p, ul, ol, table, strong, em) and a limited set of inline styles. This is exactly why clean, semantic editor output matters more for Word export than for PDF.
Option B: Pandoc for batch and server jobs
If you're exporting thousands of documents in a backend job, or you need a branded Word template, Pandoc is hard to beat. pandoc input.html -o output.docx --reference-doc=brand-template.docx applies your company's heading styles, fonts, and colors from a template file that a designer can edit in Word itself.
It's a system binary, not an npm package, so it suits a worker container better than a serverless function.
Option C: Build the document with the docx library
The docx npm package lets you build Word files node by node in code. You get total control: headers per section, tables of contents, custom numbering. You also give up HTML entirely and write a walker that turns your editor's content tree into Paragraph and TextRun objects. Worth it for invoice and contract generators. Overkill for "download my notes."
Should you build export yourself or pay for an editor plugin?
Several commercial editors sell export as a paid add-on. If you're weighing them, our guide to why you shouldn't build on contentEditable covers the build-vs-buy question for the editor itself. Here's how the main ones handle it, based on their current docs:
- TinyMCE: Export to Word is a paid add-on to a TinyMCE subscription, and it needs a converter service, either Tiny Cloud with JWT authentication or a self-hosted service you arrange with Tiny.
- Tiptap: DOCX export is a Pro extension (
@tiptap-pro/extension-export-docx) that requires a trial or a paid plan and access to Tiptap's private npm registry. - CKEditor: Export to PDF and Word run through CKEditor's converter services (cloud or on-premises), and files are watermarked without a license key.
Those plugins are convenient. They also put your document content through a third-party service (often a deal-breaker for healthcare or legal apps), add another line item that scales with usage, and tie your export format to one vendor.
Our position: if your editor outputs clean semantic HTML, the open-source pipeline above is two API routes and an afternoon of print-CSS tuning. You own it, it runs in your own infrastructure, and it costs nothing per export. That's the approach we recommend to Eddyter users, and it's one reason we put so much work into keeping Eddyter's HTML output clean. If you're still choosing an editor, our tested list of the top rich text editors for developers in 2026 compares the options side by side.
Add the export buttons to your UI
On the client, the button just posts the saved HTML and downloads the response:
One small detail that users notice: export the saved version and show a "Saving..." state if there are unsaved changes. Otherwise someone types a final paragraph, clicks Export, and the paragraph isn't in the file. If you haven't set up saving yet, see how to add autosave and draft recovery to a React editor.
Export checklist before you ship
Run one real document through both exports and check each of these. It takes ten minutes and catches nearly everything:
- Headings come out as real Word headings (check the Navigation pane in Word), not bold paragraphs.
- Bulleted and numbered lists keep their nesting.
- Tables don't split a row across pages.
- Every image appears, including ones uploaded more than an hour ago (expired signed URLs are a classic).
- Non-English text renders in both files.
- PDF text is selectable and searchable.
- A 30+ page document exports without timing out.
- The filename is the document title, not
download.pdf.
Frequently asked questions
Can I export rich text to PDF without a server?
Yes. Use window.print() with a print stylesheet for selectable text, or html2pdf.js for a fully automatic download. The print dialog gives better quality; html2pdf.js usually produces an image-based PDF where the text can't be selected or searched.
What's the best library to convert HTML to Word in JavaScript?
For most apps, @turbodocx/html-to-docx. It converts an HTML string directly to .docx in Node or the browser and supports headers, footers, page numbers, margins, and table options. Use the docx package when you need to build documents programmatically, and Pandoc for batch jobs with branded templates.
Why do my images disappear in the exported PDF or Word file?
The converter can't reach them. Relative paths, expired signed URLs, and images behind authentication all fail. Convert every image to a base64 data URI before conversion, so the file is self-contained and the converter needs no network access.
Does Eddyter have built-in PDF or Word export?
Eddyter gives you clean HTML through its onChange callback, which you can send to Puppeteer for PDF and to an HTML-to-DOCX converter for Word, as shown in this guide. That keeps document content inside your own infrastructure instead of a third-party conversion service.
How do I stop tables and headings breaking across PDF pages?
Add print CSS before rendering: break-inside: avoid on tables, images, and code blocks, and break-after: avoid on headings. In Word export, set the table row option cantSplit: true.
The bottom line
Exporting rich text editor content to PDF and Word in 2026 comes down to one rule: get clean, sanitized HTML first, then pick the right converter for each format. Puppeteer for PDF, @turbodocx/html-to-docx for Word, and a print stylesheet that respects page breaks.
The editor you start with decides how much cleanup you'll do. Eddyter outputs semantic HTML out of the box, sets up in about 10 minutes across React, Next.js, Vue, Angular, Svelte, and more, and costs a flat subscription instead of per-load pricing. Read the docs or see pricing and try it free.

Written by
Shreya Taneja
Project Manager

