Universal document converter — MIT licensed, WASM-first, written in Rust.
docmux converts between markup formats using a modular Reader → AST → Writer pipeline. Each format is a separate Rust crate, enabling tree-shaking in WASM builds. Think of it as a focused, MIT-licensed alternative to Pandoc, designed for embedding in web applications via WebAssembly.
| Format | Reader | Writer |
|---|---|---|
| Markdown (CommonMark + GFM) | ✅ | ✅ |
| HTML5 | ✅ | ✅ |
| LaTeX | ✅ | ✅ |
| Typst | ✅ | ✅ |
| MyST Markdown | ✅ | ✅ |
| DOCX | ✅ | ✅ |
| Plaintext | — | ✅ |
# Install from source
cargo install --path crates/docmux-cli
# Convert Markdown to HTML
docmux input.md -o output.html
# Standalone HTML with math support
docmux paper.md -o paper.html --standalone --math=katex
# LaTeX to Typst
docmux paper.tex -t typst -o paper.typ
# DOCX to Markdown
docmux report.docx -o report.md
# Dump AST as JSON
docmux input.md -t jsonnpm install @docmux/wasmimport { convert, convertStandalone } from "@docmux/wasm";
const result = await convert("# Hello\n\nWorld!", "markdown", "html");
if (result.error) {
console.error(result.error);
} else {
console.log(result.output); // <h1>Hello</h1>\n<p>World!</p>
}For Vite or vanilla JS without bundler plugins, use the web target:
import init, { convert } from "@docmux/wasm/web";
await init();
const html = convert("# Hello", "markdown", "html");For LaTeX papers that split their body across multiple files, pass a files
map alongside the main source:
import init, { convertWithFiles } from '@docmux/wasm/web';
await init();
const files = new Map([
['intro.tex', new TextEncoder().encode(introSource)],
['body.tex', new TextEncoder().encode(bodySource)],
]);
const md = convertWithFiles(
mainTex,
'latex',
'markdown',
files,
new Map(), // resources (images) — empty if none
false, // standalone
);\input{intro} resolves against intro.tex (the .tex extension is added
automatically if the bare key is missing). The CLI does the same thing
automatically when you point it at a single .tex file on disk:
docmux paper/main.tex --to markdownSee the @docmux/wasm README for full API documentation.
use docmux_reader_markdown::MarkdownReader;
use docmux_writer_html::HtmlWriter;
use docmux_core::{Reader, Writer, WriteOptions};
let reader = MarkdownReader::new();
let writer = HtmlWriter::new();
let doc = reader.read("# Hello\n\nWorld!")?;
let html = writer.write(&doc, &WriteOptions::default())?;- 13+ block types, 16+ inline types — math, citations, cross-references, admonitions, tables, footnotes
- Syntax highlighting via syntect (HTML and LaTeX output)
- Template engine — pandoc-compatible
$variable$syntax with conditionals and loops - Transforms — table of contents, section numbering, cross-reference resolution, CSL citations, math notation conversion, section divs
- Image embedding — real dimension parsing (PNG/JPEG), drag-and-drop in playground, resource-aware DOCX writer
- CLI parity —
--standalone,--toc,--number-sections,--template,--bibliography,--csl,--math,--css,--wrap, and more
Input → Reader → [Document AST] → Transforms → Writer → Output
The AST is format-agnostic: N readers × M writers give N×M conversions without N×M converters. Each reader, writer, and transform is a separate crate under crates/.
Contributions are welcome! Here's how to get started:
- Rust (latest stable)
- wasm-pack (for WASM builds)
- Node.js 18+ and pnpm (for the playground and npm package)
# Rust workspace
cargo check --workspace
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --check
# WASM build
cargo build --target wasm32-unknown-unknown -p docmux-wasm
# npm package
cd npm && pnpm install && pnpm run build && node test.mjs
# Playground
cd playground && pnpm install && pnpm run dev- No
unwrap()in library code — use?and proper error types - No
anyin TypeScript — use interfaces, generics, discriminated unions - New functionality must have tests
- Run
cargo fmtandcargo clippybefore committing
- Fork the repo and create a branch
- Make your changes with tests
- Ensure all checks pass:
cargo test --workspace && cargo clippy --workspace --all-targets -- -D warnings - Open a pull request with a clear description
MIT — see LICENSE.