mammoth
Mammoth converts Microsoft Word .docx documents into semantic HTML by mapping Word paragraph and character styles to elements such as headings, paragraphs, lists, links, tables, footnotes, and images. It can also extract plain text, accept files or buffers in Node, accept ArrayBuffer input in browsers, and apply custom style maps. Its goal is clean content structure rather than visual imitation of Word. That makes it useful for publishing prose authored in Word, but unsuitable for documents whose meaning depends on exact pagination, floating objects, columns, text boxes, or page layout.
Mammoth is excellent for turning disciplined, style-driven Word prose into clean web content. It is not a Word renderer, and any upload pipeline must add sanitization, resource limits, message handling, and deliberate image storage.
Use it if
- You need semantic HTML from reasonably styled .docx articles, reports, or knowledge-base pages
- You can standardize Word style names and maintain a style map for your publishing model
- You need Node file or buffer conversion and browser ArrayBuffer conversion from the same package
- You want to extract document text or images without running Microsoft Office
- You need pixel-faithful Word rendering: the README says complicated document conversion is unlikely to be perfect and prioritizes semantic information over styling
- You plan to inject output from untrusted uploads directly into a page: Mammoth explicitly performs no sanitization and warns about javascript: links
- You want direct Markdown output: convertToMarkdown is deprecated and the README recommends HTML followed by a separate converter
- You need a stable document-AST transformation API: transformDocument is explicitly marked unstable and may change between any versions
- You need safe access to linked external files: access is disabled by default, and enabling externalFileAccess can allow server files to be read and exfiltrated
Setup reality
mammoth 1.12.0 requires Node 12 or newer, bundles TypeScript declarations, and depends on a sizeable pure-JavaScript stack for ZIP and WordprocessingML parsing. In Node, pass {path} or {buffer}; in a browser, pass {arrayBuffer}. Mixing those input shapes is a common first failure. convertToHtml returns a promise containing value and messages, and warnings do not necessarily reject the promise, so log or surface result.messages instead of assuming success means fidelity. The default style map handles common Word styles, but real editorial pipelines need authors to use named styles consistently. Direct formatting and creative document layout do not translate predictably. Custom mappings override defaults; includeDefaultStyleMap: false removes all built-ins, while includeEmbeddedStyleMap: false prevents a document-carried mapping from changing server behavior. Images become data URIs by default, which can make HTML very large. Production systems usually save image buffers to controlled storage and return trusted URLs. Browser image handlers cannot call readAsBuffer without a Buffer polyfill, so use readAsArrayBuffer or readAsBase64String there. The security warning is not optional: Mammoth does no sanitization, .docx links can contain javascript: URLs, and crafted documents may cause pathological CPU or memory use. Keep externalFileAccess false for uploads, sanitize generated HTML with a separate allowlist sanitizer, cap input size, and isolate conversion behind a timeout or worker when documents are untrusted. idPrefix defaults to empty, so multiple converted documents can collide on bookmark, footnote, or endnote IDs when rendered together. Set a unique safe prefix. Markdown conversion is deprecated; convert to HTML and then use a maintained HTML-to-Markdown package. If layout is the requirement, use a renderer or server-side Office/PDF conversion rather than adding more style-map exceptions.
Patterns
Convert a .docx path to HTMLconvert-file
const mammoth = require('mammoth');
const result = await mammoth.convertToHtml(
{ path: 'article.docx' },
{ idPrefix: 'article-42-' }
);
console.log(result.value);
for (const message of result.messages) console.warn(message.type, message.message);Warnings are returned in messages and may not reject the promise. Set idPrefix when several documents can share a page.
Convert an uploaded Node bufferconvert-buffer
const result = await mammoth.convertToHtml(
{ buffer: uploadedBuffer },
{
externalFileAccess: false,
includeEmbeddedStyleMap: false,
idPrefix: requestId + '-',
}
);Keep externalFileAccess false for uploads. Disabling embedded style maps prevents the document from supplying conversion rules.
Convert a browser Fileconvert-in-browser
import mammoth from 'mammoth/mammoth.browser';
const arrayBuffer = await file.arrayBuffer();
const result = await mammoth.convertToHtml({ arrayBuffer });
preview.innerHTML = DOMPurify.sanitize(result.value);Browser input uses arrayBuffer, not path or Node Buffer. DOMPurify is a separate dependency; Mammoth output is not sanitized.
Map editorial Word styles to semantic HTMLmap-word-styles
const options = {
styleMap: [
"p[style-name='Article Title'] => h1:fresh",
"p[style-name='Deck'] => p.deck:fresh",
"p[style-name='Pull Quote'] => blockquote:fresh",
],
};
const result = await mammoth.convertToHtml({ path }, options);Style names must match the source document. Custom mappings take precedence over the default map.
Disable built-in and embedded mappingsuse-only-custom-map
const result = await mammoth.convertToHtml(
{ buffer },
{
styleMap: styleMapText,
includeDefaultStyleMap: false,
includeEmbeddedStyleMap: false,
}
);With defaults disabled, every structure you care about must be covered by your map or Mammoth's fallback behavior.
Extract text without formattingextract-raw-text
const { value: text, messages } = await mammoth.extractRawText({ buffer });
console.log(text);Formatting is ignored and each paragraph is followed by two newlines. This is extraction, not layout-preserving conversion.
Save extracted images instead of embedding data URLsstore-images
const fs = require('node:fs/promises');
let imageNumber = 0;
const options = {
convertImage: mammoth.images.imgElement(async (image) => {
const extension = image.contentType === 'image/png' ? 'png' : 'bin';
const name = 'image-' + (++imageNumber) + '.' + extension;
await fs.writeFile('public/uploads/' + name, await image.readAsBuffer());
return { src: '/uploads/' + name };
}),
};Validate content types, generate collision-safe names, and write outside executable paths in real upload systems. readAsBuffer is Node-only.
Expose Word comments in the outputinclude-comments
const result = await mammoth.convertToHtml(
{ path },
{ styleMap: ['comment-reference => sup'] }
);Comments are ignored by default. When included, comment bodies are appended to the document and references use the mapped element.
Keep empty Word paragraphspreserve-empty-paragraphs
const result = await mammoth.convertToHtml(
{ path },
{ ignoreEmptyParagraphs: false }
);Empty paragraphs are ignored by default. Preserving them can create presentation-only markup that conflicts with semantic publishing goals.
Sanitize generated HTML on the serversanitize-output
const sanitizeHtml = require('sanitize-html');
const result = await mammoth.convertToHtml({ buffer }, {
externalFileAccess: false,
includeEmbeddedStyleMap: false,
});
const safeHtml = sanitizeHtml(result.value, {
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
allowedSchemes: ['http', 'https', 'mailto'],
});sanitize-html is a separate install. Keep javascript and data schemes out unless a reviewed use case requires them.
Write a reusable style map into a documentembed-style-map
const fs = require('node:fs/promises');
const mapped = await mammoth.embedStyleMap(
{ path: 'source.docx' },
"p[style-name='Section Title'] => h1:fresh"
);
await fs.writeFile('mapped.docx', mapped.toBuffer());Embedded maps are consumed by default during later conversion. Disable includeEmbeddedStyleMap when documents are untrusted.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| docx-preview | npm | You want browser-side visual rendering that tries to resemble the original Word layout |
| officeparser | npm | You mainly need text extraction from several Office and OpenDocument formats |
| textract | npm | You need a common text-extraction interface across office files, PDFs, and other document types and accept external-tool setup |