# mammoth converts a Word document by reading its styles, not its appearance

> mammoth turns .docx files into simple HTML by looking at the semantic information in the document, so a paragraph with the Heading 1 style becomes an h1 element and the font, size and colour are dropped on purpose. The Python package ships a CLI, a library with custom style maps and custom image handlers, and it warns twice in the documentation that it performs no sanitisation of the source document.

**mwilliamson/python-mammoth** — Convert Word documents (.docx files) to HTML

- Repository: https://github.com/mwilliamson/python-mammoth
- Stars: 1,120 · Forks: 153
- Language: Python
- License: BSD-2-Clause
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/mwilliamson-python-mammoth

## Semantic information in, presentation out

The design decision is stated in the first paragraph of the documentation. Mammoth is designed to convert .docx documents, such as those created by Microsoft Word, Google Docs and LibreOffice, and convert them to HTML, and it aims to produce simple and clean HTML by using semantic information in the document and ignoring other details. The example that makes it concrete is a heading. Mammoth converts any paragraph with the style Heading 1 to h1 elements, rather than attempting to exactly copy the styling of the heading, such as its font, text size and colour. That is a deliberate refusal to reproduce the document, and it has a consequence that decides whether you want this library, which is that the output is a document, not a picture of a document. If your use case is publishing the content, that is the right trade. If your use case is showing somebody the exact document they sent you, it is the wrong one, and the mismatch is not a bug you can configure away.

## The structural mismatch, stated honestly

The documentation does not oversell this. There is a large mismatch between the structure used by .docx and the structure of HTML, meaning that the conversion is unlikely to be perfect for more complicated documents, and Mammoth works best if you only use styles to semantically mark up your document. That is the contract, stated in two sentences, and it is worth reading before anything else because it tells you that the quality of the output is a property of the input. A document whose authors used Heading 1, List Paragraph and Quote styles converts well. A document where somebody typed a heading, made it bold, changed its size to twenty-two points and coloured it blue converts to a paragraph, because none of that is semantic. The same applies to tables, where the formatting of the table itself, such as borders, is currently ignored, while the formatting of the text is treated the same as in the rest of the document. So a table keeps its bold cells and loses its grid.

## What the conversion actually covers

The supported feature list is the reference for what you can expect. Headings and lists are there. Customisable mapping from your own docx styles to HTML is there, with the example that you could convert WarningHeading to h1.warning by providing an appropriate style mapping. Tables are there, with the caveat about borders just described. Footnotes and endnotes are there. Images are there, as are bold, italics, underlines, strikethrough, superscript and subscript, along with links and line breaks. Two of the entries are worth reading twice. Text boxes are supported, but the contents of the text box are treated as a separate paragraph that appears after the paragraph containing the text box, which is a reasonable compromise given that HTML has no equivalent construct. Comments are supported as well, so the annotation layer of a document survives the conversion. What is absent from the list is as informative as what is present, since there is no mention of page layout, columns, headers and footers, or of any of the drawing features that make a Word document look like itself.

## The command line, and the fragment it writes

The CLI converts a document by passing the path to the docx file and the output file:

```bash
pip install mammoth
```

```bash
mammoth document.docx output.html
```

If no output file is specified, the output is written to stdout instead, which is what makes it usable in a pipeline. The output is an HTML fragment rather than a full HTML document, encoded with UTF-8, and the documentation is precise about the consequence, that since the encoding is not explicitly set in the fragment, opening the output file in a web browser may cause Unicode characters to be rendered incorrectly if the browser does not default to UTF-8. Images are included inline in the output HTML by default, and if an output directory is specified by --output-dir the images are written to separate files instead, with existing files overwritten if present. A custom style map can be read from a file with --style-map, and Markdown output is still available through --output-format=markdown although that support is deprecated, with the recommendation to generate HTML and use a separate library to convert the HTML to Markdown, which is likely to produce better results.

## Style maps are how you control the output

The style map is the mechanism that turns a general converter into one that fits your documents, and the syntax is compact. A custom style map can be read from a file using --style-map, and the file looks something like:

```text
p[style-name='Aside Heading'] => div.aside > h2:fresh
p[style-name='Aside Text'] => div.aside > p:fresh
```

In the library, the same map is a string passed as an options object with a style_map property as a second argument to convert_to_html:

```python
import mammoth

style_map = """
p[style-name='Section Title'] => h1:fresh
p[style-name='Subsection Title'] => h2:fresh
"""

with open("document.docx", "rb") as docx_file:
    result = mammoth.convert_to_html(docx_file, style_map=style_map)
```

Two details in that example are worth understanding. The :fresh marker means the element is created rather than matched against existing content, which is how the mapping controls nesting. And user-defined style mappings are used in preference to the default style mappings, so your rules win, and to stop using the defaults altogether you pass include_default_style_map=False. The built-in mappings can also be overridden one at a time, with a style map of b => em wrapping bold text in em tags instead of the default strong.

## The library returns a value and a list of messages

The library API is small, and its shape is the useful part. To convert an existing .docx file to HTML you pass a file-like object to mammoth.convert_to_html, and the file should be opened in binary mode:

```python
import mammoth

with open("document.docx", "rb") as docx_file:
    result = mammoth.convert_to_html(docx_file)
    html = result.value
    messages = result.messages
```

The result carries two things, the generated HTML and a list of messages, such as warnings during conversion, and that second field is what you want in a pipeline, because a document that converted with warnings usually means somebody used formatting where a style was expected. The same shape works for raw text, where mammoth.extract_raw_text ignores all formatting in the document and each paragraph is followed by two newlines. Images are handled by a convert_image argument that takes an image converter, and the default behaviour, which converts images to img elements with the source included inline in the src attribute, can be reproduced with mammoth.images.img_element, so a pipeline that wants to rewrite image sources, to upload them somewhere or to resize them, plugs in at exactly one point.

## No sanitisation, and the same idea in four languages

The security note is repeated, once in the CLI section and once in the library section, and it is not a boilerplate warning. Mammoth performs no sanitisation of the source document, and should therefore be used extremely carefully with untrusted user input. The mechanism is easy to see from the features list, since a .docx can contain comments, and a converter that emits those comments faithfully will emit whatever a comment contains, so the output of a document from a stranger is HTML that a stranger influenced. If your use case involves user uploads, the output needs sanitising after conversion, and that step is yours. The other thing to notice is the shape of the project family. The same idea is implemented in JavaScript for both the browser and Node, published on npm, in Java for the JVM on Maven Central, in .NET on NuGet, and as a WordPress plugin, and the Python package is version 1.13.0 with a single runtime dependency, cobble, and Python 3.7 and later. A design that is ported to four platforms is a design that is understood rather than one that is discovered, and the maintenance picture is that the repository is not archived, the last push was on 2026-09-26, and the release history lives in a NEWS file rather than in GitHub releases.

## Conclusion

mammoth is the right tool when you are publishing documents that people wrote with styles rather than with direct formatting, because a document authored that way converts cleanly and a document authored with manual formatting does not, and that difference is the whole design. It is the wrong tool for a document whose visual appearance is the point, since every font, colour and border is discarded, and it is the wrong tool for untrusted uploads unless you sanitise the output yourself, because the documentation says twice that no sanitisation is performed. Before adopting it, look at a real document from your users rather than a sample, since the result depends entirely on whether the authors used styles, and read the security section before you wire it into anything that takes a file from a stranger.

## FAQ

### How do I install and run mammoth?

Install it with pip install mammoth, then run mammoth document.docx output.html to write an HTML file, or pass no output file to write to stdout. The output is an HTML fragment encoded with UTF-8, not a complete HTML document.

### Why does my converted document lose its formatting?

Mammoth converts by semantic information and ignores presentation details, so a paragraph styled as Heading 1 becomes an h1 and its font, size and colour are dropped. It works best if you only use styles to semantically mark up your document, and there is a large mismatch between docx structure and HTML structure for more complicated documents.

### How do I map my own Word styles to HTML elements?

With a style map, passed in the library as the style_map option of convert_to_html, or read from a file with the --style-map flag on the command line. A mapping looks like p[style-name='Section Title'] => h1:fresh, and user-defined mappings take precedence over the defaults, which you can switch off entirely with include_default_style_map=False.

### Is it safe to convert documents from untrusted users with mammoth?

No, not on its own. The documentation states that Mammoth performs no sanitisation of the source document and should therefore be used extremely carefully with untrusted user input, so the generated HTML needs sanitising before it is served.

## Sources

- [Issues](https://github.com/mwilliamson/python-mammoth/issues)
- [License: BSD-2-Clause](https://github.com/mwilliamson/python-mammoth/blob/master/LICENSE)
- [mwilliamson/python-mammoth on GitHub](https://github.com/mwilliamson/python-mammoth)
- [README](https://github.com/mwilliamson/python-mammoth/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mwilliamson-python-mammoth
