Open-source project
bokuweb/docx-rs avatar
bokuweb/docx-rs

docx-rs: reading and writing .docx files from Rust and from the browser

:memo: A .docx file writer with Rust/WebAssembly.

560 stars122 forksRustMIT

At a glance

What is it?
docx-rs is a Rust library for producing and parsing Office Open XML documents, with a WebAssembly build published to npm so the same writer runs in a browser or in Node. The README is blunt that OOXML is a large specification and support is not exhaustive, which is the honest framing for a document format this wide.
Who is it for?
docx-rs fits a Rust service that has to emit .docx reports, or a browser application that has to build a document client side without shipping a desktop toolchain. It is the wrong choice if you need to round trip every feature of Word, since the README states plainly that support is not exhaustive for OOXML, and the wrong choice if a template driven document is enough, because a template plus a PDF renderer avoids the whole problem.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One writer, two runtimes, Rust and the browser

docx-rs is described as a .docx file writer with Rust and WebAssembly, and the README summarises the aim as reading and writing .docx files with Rust, WebAssembly, and JavaScript. That second half is the interesting part. Document generation is normally a server-side job, because the code that builds a .docx is usually a backend service, and the browser case is where things get awkward. The repository layout shows the split: a `docx-core/` directory and a `docx-wasm/` directory, and the workspace `Cargo.toml` lists those two as its members. The npm package is `docx-wasm`, and the crate is `docx-rs`, and the browser example is built on the same API. The audience is therefore two groups at once, a Rust backend developer and a front-end developer who needs a document without a round trip to a server.

Adding it is a dependency line, or a package install

The installation section is split by target. For Rust you add the dependency to your manifest:

toml
[dependencies]
docx-rs = "0.4"

For the browser or Node.js you install the WebAssembly package:

sh
pnpm add docx-wasm

The browser example also uses `file-saver`, which the README calls out separately:

sh
pnpm add file-saver

The version constraint is the part to notice. The dependency is `docx-rs = "0.4"`, and the releases listed run 0.4.21 from 2026-07-20, 0.4.19 from 2026-02-08, and 0.4.17 from 2024-04-26, so the 0.4 line has been active for a while and is where a new adopter should start. The licence is MIT, and the project home page is at bokuweb.github.io/docx-rs with the API reference on docs.rs.

pack writes the file, build is for when you need the XML

The writing API has a small distinction that the README is careful about. `Docx::pack` writes the package directly to the output file, and you should use `Docx::build().pack(...)` only when you need access to the rendered XML package before it is archived. That is the whole difference between the two forms, and it matters if you plan to post-process the document before it becomes a file. The basic Rust example is short:

rust
use docx_rs::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = std::fs::File::create("./hello.docx")?;

    Docx::new()
        .add_paragraph(Paragraph::new().add_run(Run::new().add_text("Hello")))
        .pack(file)?;

    Ok(())
}

The shape is a builder. `Docx::new()` starts a document, `add_paragraph` appends content built from `Paragraph`, `Run`, and `add_text`, and `pack` writes it out. The `?` operator and the boxed error type are ordinary Rust, and the example is a complete program you can paste into a file and run.

Reading parses into the same model the writer uses

The reading side is not a separate object model. `read_docx` parses a DOCX package into the same `Docx` model used by the writer, and the model can also be serialized to JSON. There is one security-shaped rule: packages whose ASCII ZIP part names conflict with their resolved filenames are rejected. That is worth understanding, because a .docx is a ZIP archive, and a filename that does not match its internal name is a classic way to smuggle content past an unzip step.

rust
use docx_rs::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let bytes = std::fs::read("./hello.docx")?;
    let docx = read_docx(&bytes)?;

    println!("{}", docx.json());
    Ok(())
}

The repository root contains `hello.docx` and `hello.json` as fixtures, which is a quick way to see what the parser produces from a file the writer created.

Image previews are on by default and can be turned off

One parsing option is worth knowing about because it has a performance consequence. Image previews are generated by default, and if you only need the original embedded image bytes you can disable preview generation to avoid decoding and re-encoding them:

rust
let docx = read_docx_with_options(
    &bytes,
    ReadDocxOptions::default().with_image_previews(false),
)?;

The default is the right default for a document viewer, where you want to show a preview, and the wrong default for a service that is extracting images to re-upload them elsewhere, where the decode and re-encode is work you did not ask for. The two APIs differ only in the options argument, which is the way this library tends to expose behaviour changes.

The WebAssembly API mirrors the Rust one almost line for line

The JavaScript examples are the Rust examples with the types renamed, and that is the useful part of the design. In the browser:

javascript
import { saveAs } from "file-saver";

import("docx-wasm").then((w) => {
  const buffer = new w.Docx()
    .addParagraph(
      new w.Paragraph().addRun(new w.Run().addText("Hello world!"))
    )
    .build();

  saveAs(new Blob([buffer]), "hello.docx");
});

In Node.js it is the same chain with the file written directly:

javascript
const w = require("docx-wasm");
const { writeFileSync } = require("fs");

const buffer = new w.Docx()
  .addParagraph(new w.Paragraph().addRun(new w.Run().addText("Hello world!")))
  .build();

writeFileSync("hello.docx", Buffer.from(buffer));

Note the naming difference between the two runtimes, which is a small trap. The Rust side offers `pack` to write straight to a file, while the WebAssembly side exposes `build`, which returns a buffer you save yourself. The browser build is loaded as a dynamic import, so the WebAssembly module is fetched at runtime rather than bundled at build time.

What the library covers, and where the coverage stops

The supported capabilities list is the honest inventory of the project. The Rust API supports paragraphs, runs, text formatting, borders, tabs, and breaks, tables with nested tables, numbering, styles, and theme colors, inline and floating images, headers, footers, page settings, and sections, hyperlinks, bookmarks, comments, footnotes, and tracked changes, and tables of contents, structured data tags, custom properties, and custom XML. The WebAssembly package exposes document generation and DOCX to JSON parsing for browser and Node.js applications. Then comes the sentence that should shape your expectations, because OOXML is a large specification and support is not exhaustive. One detail shows the level of care behind the parsing: footnote reference parsing preserves `w:id` and optional `w:customMarkFollows` without consuming adjacent text, the Rust `FootnoteReference::custom_mark_follows` builder writes the flag, and JSON includes `customMarkFollows` only when it is specified.

Where docx-rs is the wrong tool

Two limits are worth being clear about. The first is coverage, and it is not a gap that will close for you on a deadline. A document format with tracked changes, structured data tags, and custom XML has hundreds of features, and the library supports a large subset of them. If your organisation has Word templates with specific content controls, or needs round tripping that preserves a document exactly, then generating documents with this library and expecting Word to see no differences is the wrong plan. The second is that a document generator is a lot of work when a template would do. If your output is a report with a fixed layout, a template file plus a PDF renderer gives you a stable result with none of this. The value of docx-rs shows up when the document structure is computed at runtime, when the document has to be produced in the browser, or when you want to read documents back and work with their contents as data.

Editorial conclusion

docx-rs fits a Rust service that has to emit .docx reports, or a browser application that has to build a document client side without shipping a desktop toolchain. It is the wrong choice if you need to round trip every feature of Word, since the README states plainly that support is not exhaustive for OOXML, and the wrong choice if a template driven document is enough, because a template plus a PDF renderer avoids the whole problem. Before you adopt it, read the supported capabilities list against the features your documents actually use, and check the version pairing, since the crate is on the 0.4 line with the newest release 0.4.21 and the workspace builds both the Rust crate and the WebAssembly package from the same source.

Frequently asked questions

How do I add docx-rs to a Rust project?

Add `docx-rs = "0.4"` to the `[dependencies]` section of your Cargo.toml. For the browser or Node.js you install the WebAssembly package instead with `pnpm add docx-wasm`, and the browser example additionally uses `file-saver`.

What is the difference between Docx::pack and Docx::build in docx-rs?

`Docx::pack` writes the package directly to the output file, and the README says to use `Docx::build().pack(...)` only when you need access to the rendered XML package before it is archived. The WebAssembly build exposes `build` and returns a buffer that you save yourself.

Can docx-rs read existing .docx files?

Yes. `read_docx` parses a DOCX package into the same `Docx` model used by the writer, and the model can be serialized to JSON. Packages whose ASCII ZIP part names conflict with their resolved filenames are rejected, and image previews are generated by default unless you pass `ReadDocxOptions::default().with_image_previews(false)`.

Is docx-rs able to produce every Word feature?

No. The README states that OOXML is a large specification, so support is not exhaustive. The listed capabilities cover paragraphs and runs, tables and nested tables, numbering, styles and theme colors, inline and floating images, headers, footers, page settings and sections, hyperlinks, bookmarks, comments, footnotes, tracked changes, tables of contents, structured data tags, custom properties, and custom XML.

Official sources

  1. bokuweb/docx-rs on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bokuweb-docx-rs.svg)](https://hysenlabs.com/projects/bokuweb-docx-rs)

Was this useful?