How to Preview DOCX Files in JavaScript
Choose Mammoth for semantic HTML or docx-preview for a document-like browser view, with runnable JavaScript examples and production guidance.
Direct answer: use Mammoth.js when you want semantic HTML that fits into your application layout. Use docx-preview when you want a read-only, page-like rendering inside the browser. Neither approach promises pixel-perfect Microsoft Word reproduction.
A browser cannot display a DOCX file by itself. Your application must parse the ZIP/XML document and either convert its structure to HTML or render that structure into a document-style container. The right choice depends on whether content structure or visual page layout matters more.
Choose the right approach
| Requirement | Recommended route | Tradeoff |
|---|---|---|
| Readable content integrated into your page | Mammoth.js | Semantic HTML is clean, but detailed Word styling can be lost. |
| Read-only pages that resemble a document | docx-preview | Common document features render in the browser, but pagination and field limitations remain. |
| Interaction with a document inside Word or another Office host | Office.js | Useful for add-ins, not a general standalone DOCX viewer; support varies by host and platform. |
| Exact Word output | Reconsider the requirement | The cited browser libraries do not establish pixel-perfect Word rendering. |
Preview a DOCX as semantic HTML with Mammoth
Mammoth maps Word structure to HTML. For example, a paragraph styled as “Heading 1” becomes an h1. It is designed to produce useful semantic content rather than copy every font, spacing, and page-layout detail. The maintainers also caution that complicated documents may not convert perfectly. Mammoth supports headings, lists, tables, notes, images, links, text formatting, line breaks, text boxes, and comments. See the project documentation for the current API.
Install it
npm install mammoth
Browser application example
This example lets a user choose a local DOCX file, converts it, displays conversion messages, and inserts the resulting HTML into a preview element.
import mammoth from 'mammoth';
const picker = document.querySelector('#docx-file');
const preview = document.querySelector('#preview');
const messages = document.querySelector('#messages');
picker.addEventListener('change', async () => {
const file = picker.files?.[0];
if (!file) return;
preview.replaceChildren();
messages.replaceChildren();
try {
const arrayBuffer = await file.arrayBuffer();
const result = await mammoth.convertToHtml({ arrayBuffer });
// Sanitize before inserting when the file is not fully trusted.
preview.innerHTML = result.value;
for (const message of result.messages) {
const item = document.createElement('li');
item.textContent = `${message.type}: ${message.message}`;
messages.append(item);
}
} catch (error) {
messages.textContent = `Could not preview this file: ${error.message}`;
}
});
<input id='docx-file' type='file' accept='.docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document'>
<ul id='messages'></ul>
<article id='preview'></article>
Sanitize untrusted uploads
Mammoth explicitly does not sanitize source documents. If users can upload files, treat the returned HTML as untrusted. Sanitize it with a policy appropriate to your application before assigning innerHTML. For example, with DOMPurify:
import DOMPurify from 'dompurify';
const result = await mammoth.convertToHtml({ arrayBuffer });
preview.innerHTML = DOMPurify.sanitize(result.value, {
USE_PROFILES: { html: true }
});
Do not bypass your content security policy or allow arbitrary scripts, event-handler attributes, or unsafe URLs merely because the source file has a DOCX extension.
Control style mappings
You can map custom Word styles to HTML elements when the document uses organization-specific names.
const result = await mammoth.convertToHtml(
{ arrayBuffer },
{
styleMap: [
"p[style-name='Warning'] => blockquote.warning",
"p[style-name='Code'] => pre:separator('\n')"
]
}
);
Keep mappings focused on structure. A style map does not turn Mammoth into a Word layout engine.
Render a document-like preview with docx-preview
docx-preview renders a parsed DOCX into a DOM container. Its documented common support includes body text and paragraph styling, lists, tables, inline images, hyperlinks, headers, footers, and notes. It is read-only. The wrapper documentation lists important gaps: no live repagination, source-declared page breaks rather than a full Word pagination algorithm, and fields such as TOC or PAGE using cached display values when present (otherwise instructions may appear). Pixel-perfect Word rendering is explicitly out of scope.
Install and render
npm install docx-preview
import { renderAsync } from 'docx-preview';
const picker = document.querySelector('#docx-file');
const container = document.querySelector('#document-pages');
picker.addEventListener('change', async () => {
const file = picker.files?.[0];
if (!file) return;
container.replaceChildren();
try {
const buffer = await file.arrayBuffer();
await renderAsync(buffer, container);
} catch (error) {
container.textContent = `Could not render this document: ${error.message}`;
}
});
If you use the office-kit wrapper, its previewToDOM API accepts a parsed DOCX value or raw Uint8Array, Blob, or ArrayBuffer. It returns a handle with dispose(); call that handle when replacing a preview in a long-lived page.
import { previewToDOM } from 'office-kit';
let currentPreview;
async function showDocument(data, element) {
currentPreview?.dispose();
currentPreview = await previewToDOM(data, element);
}
Basic page styling
#document-pages {
overflow: auto;
background: #e5e7eb;
padding: 2rem;
}
#document-pages section {
margin: 0 auto 1.5rem;
background: white;
box-shadow: 0 1px 5px rgb(0 0 0 / 20%);
}
Use styling to make the preview comfortable to read, but avoid promising that CSS changes reproduce Word’s pagination. Browser font availability, viewport width, and CSS layout all affect the result.
Load DOCX files from a server
Fetch the file as an ArrayBuffer, then pass the bytes to either library. Configure the server to return the DOCX media type and enforce authentication and size limits before the browser downloads it.
async function loadDocx(url) {
const response = await fetch(url, { credentials: 'include' });
if (!response.ok) throw new Error(`Download failed (${response.status})`);
return response.arrayBuffer();
}
const bytes = await loadDocx('/api/documents/123/download');
const result = await mammoth.convertToHtml({ arrayBuffer: bytes });
For cross-origin files, the document host must send appropriate CORS headers. Do not expose private document URLs in public page markup unless that is intentional.
Office.js: when the document is inside Office
Office.js lets an Office add-in interact with the document in the Office application where the add-in runs. It is relevant when your preview is part of Word or another supported Office host. It is not the default solution for displaying an arbitrary uploaded DOCX in a standalone web application. Microsoft documents that API availability varies by application, version, and platform. Microsoft also labels Word preview APIs as subject to change and not intended for production or business-critical documents.
Fidelity, security, and file coverage
- Semantic conversion: Mammoth favors headings, lists, tables, and other structure over exact typography and page geometry.
- Page-like rendering: docx-preview handles common content but documents limits around repagination, fields, tab stops, and list edge cases.
- Complex fixtures: Test representative files containing tables, images, page breaks, headers, footers, fields, text boxes, and custom styles before promising support.
- Untrusted input: Sanitize Mammoth output and apply a restrictive content security policy. Validate file type and size on the server as well as in the browser.
- Accessibility: Check heading order, keyboard navigation, focus behavior, contrast, and alternative text for images after conversion or rendering.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| “Unsupported file” or a parsing exception | The upload is not a valid DOCX ZIP package, is truncated, or is actually a legacy .doc file. |
Validate the file server-side and require DOCX; convert legacy files before previewing. |
| Preview is blank | The container was removed, CSS hides its contents, or rendering failed before the error was surfaced. | Clear the container, await the conversion promise, log the error, and inspect computed styles and network responses. |
| Formatting differs from Word | The chosen library targets semantic HTML or browser layout rather than Word’s full rendering engine. | Set expectations, add targeted style mappings, or choose an Office-hosted workflow when interaction in Word is required. |
| TOC or PAGE shows stale values | Fields may use cached values; live field evaluation is not provided by the renderer. | Regenerate fields before upload or display a clear limitation to users. |
| Images are missing | The document contains unsupported image data, blocked blob URLs, or a failed relationship. | Inspect console errors and the document package; test the specific image formats in your fixture set. |
| Styles look wrong after sanitization | Your sanitizer removed attributes or CSS needed by the generated markup. | Allow only the minimal safe tags and attributes your design requires, then keep CSS in your own stylesheet. |
| Large files freeze the tab | Parsing, HTML insertion, image decoding, and layout all run on the main thread. | Limit size, show progress states, defer nonessential work, and consider a server-side conversion or a worker architecture. |
| CORS error while fetching | The document origin does not permit your application origin. | Configure CORS on the document host or proxy the download through an authenticated server endpoint. |
Performance, reliability, and cost
Neither cited project supplies a reliable universal speed or fidelity benchmark. Measure with your own representative documents. Record file size, number of images, table complexity, conversion time, render time, memory use, and time to first visible content.
- Reject oversized files before parsing and give users a clear limit.
- Cache immutable document bytes or sanitized HTML when access rules allow it.
- Use a Web Worker or server-side conversion if parsing large files blocks interaction.
- Destroy old renderer instances and DOM nodes when navigating between documents.
- Keep conversion errors separate from user-facing document content so failures are observable.
- For sensitive documents, decide whether bytes may leave the browser; client-side rendering can reduce upload exposure, while server-side conversion centralizes control.
Mammoth and docx-preview are JavaScript dependencies, so your direct cost is the infrastructure that serves files and runs conversion. A hosted viewer adds provider-specific upload, privacy, and pricing considerations; verify those terms from the provider’s current documentation before selecting one.
Or skip the browser setup
If your real requirement is a screenshot of a public document preview page, ScreenshotNeo can capture the rendered page with one request. Its cleanup steps accept consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports the page verdict and billing status.
See the ScreenshotNeo API documentation for all options. cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo also provides an MCP server for Claude, Cursor, and other MCP clients, with take_screenshot, get_page_info, and capture_pdf tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Can JavaScript preview a .doc file?
The approaches here target DOCX. A legacy binary .doc file needs a separate conversion step before these browser libraries can process it.
Should I use Mammoth or docx-preview for editing?
Both approaches described here are for previewing. Use a dedicated editor or the Office host APIs when users must modify document content.
Can I guarantee identical pagination?
No. The cited documentation does not establish Word-identical output, and docx-preview documents limitations around repagination and fields.
Is inserting Mammoth output safe?
Not automatically. Mammoth states that it does no sanitization, so sanitize output from untrusted files before inserting it into the DOM.


