firecrawl/anydoc: One Document Model Behind Fourteen Office Formats
A pure Rust parser that converts Word, PowerPoint, Excel, PDF and ten other formats to the same Markdown in single-digit milliseconds. The architecture matters more than the benchmark.
Most document-to-Markdown pipelines are not one tool. They are a stack of libraries glued together by a dispatch function, one per format, each with its own idea of what a nested list looks like coming out the other side. You fix table escaping for .docx on Tuesday and rediscover the same bug in .rtf on Thursday.
firecrawl/anydoc is a bet that this is an architecture problem rather than a parsing problem. Fourteen formats, one shared document model, one Markdown serializer. Pure Rust, no ML models, no external services, MIT licensed, 5.2k stars and 225 forks as of this writing.
The speed headline is real and it is also the least interesting thing about the project.
What "one document model" actually buys you
Here is the pipeline, straight from the README. Bytes come in. Format detection reads content markers rather than the extension. A per-format parser produces a Document: blocks, inlines, tables, footnotes, assets. A single GFM serializer turns that into Markdown. PDFs take a side road through pdf-inspector, Firecrawl's other Rust parser, which converts text-based PDFs locally with no OCR service.
The consequence the README names is the one worth internalizing: a table-escaping fix for docx is automatically a table-escaping fix for rtf, odt, and everything else. Output quirks get fixed once.
If you have ever maintained a document ingestion pipeline for a RAG system, you know why that sentence is the whole pitch. The failure mode is never "the parser crashed." The failure mode is that headings from PowerPoint come out as bold paragraphs while headings from Word come out as ##, and six weeks later your chunking strategy silently works well on half the corpus.
The format list is broader than the marketing line suggests. Word covers .doc, .docx, and .docm. PowerPoint covers seven extensions including .pps and .pot. Excel includes .xlsb. OpenDocument gets text, spreadsheet, and presentation. Then RTF, EPUB, CSV, and PDF. The 2003-era binary formats are in there, which is unusual and which matters, because those are exactly the files that show up in a client's shared drive and break everything.
Detection reads the PDF header, the RTF open group, OLE stream names, and the ZIP package mimetype. A .docx someone renamed to .txt still converts. CSV is the exception, since it has no marker, so you name it explicitly.
The benchmark, read honestly
The README publishes a comparison against six other converters on 100 real-world documents spanning all fourteen formats. anydoc's median conversion is 4.4 milliseconds. markitdown is 134.8. LibreOffice is 1129.5. It was the only tool covering all fourteen formats; the next-broadest, LibreOffice, does twelve, and mammoth does one.
Trust the speed numbers. The methodology is stated plainly: one warm conversion per document on a Ryzen 9 9950X3D, process spawn excluded for anydoc and the Python libraries, included for the CLI tools since that is how people run them. That is a fair accounting, and an order-of-magnitude gap does not come from measurement error.
Do not trust the quality scores the same way, and Firecrawl mostly tells you why.
The quality column comes from an LLM judge (Claude Sonnet 5) comparing two tools' outputs blind against ground truth, where ground truth is the document's first six pages rendered to images by LibreOffice. Every pair is judged twice with positions swapped, 481 verdicts total. That is a more careful design than most vendor benchmarks bother with, and it still has two problems the README is upfront about.
First, the corpus is not redistributable and is not in the repo. You cannot reproduce the run.
Second, and this is the subtle one, each tool's headline score averages over only the formats it supports. mammoth's 69 is docx alone. anydoc's 81 spans all fourteen. The README says so directly and points you at the per-format table as the fair comparison. Read that table instead. anydoc leads every format it is measured on, which is a real result, and it is a different claim than "81 versus 69."
There is also a structural oddity worth naming. LibreOffice renders the ground truth images that the judge scores against, and LibreOffice is also one of the tools being scored. I do not think that sinks the benchmark, since LibreOffice finishes near the bottom anyway, which is the opposite of what a rigged comparison looks like. But it is the kind of detail that belongs in your head when you quote the number.
The OCR line
anydoc does not do OCR. An image-only PDF returns Unsupported.
The README is direct about where that leads: Firecrawl Parse, the hosted API, "gives you the same conversion plus our OCR models for the scanned pages anydoc can't read on its own." The free binary handles the text-based case and hands you back to a paid service at the boundary where things get expensive.
I want to be clear that this is a reasonable arrangement rather than a bait and switch. OCR needs models, models need weights, and weights do not ship in a 100-commit Rust crate with zero external dependencies. Firecrawl open-sourced the deterministic half and kept the part that costs money to run. That is an honest split.
Just plan for it. If your document flow includes scans, contracts signed on paper, or anything that came out of a fax gateway, anydoc covers the other pile and you still need a second path. The error enum makes this tractable: ConvertError distinguishes Unsupported, Malformed, Encrypted, ResourceLimit, MissingPart, and Io, so routing the scanned subset to a different service is a match arm rather than a heuristic.
Put this into practice
The lowest-friction version takes about a minute and requires no code.
npx @firecrawl/anydoc report.docx
That prints Markdown to stdout. Point it at the ugliest file you own, the one from 2011 with the merged cells. That single command tells you more than the benchmark table will.
For an agent, the install is one line:
npx skills add firecrawl/anydoc
anydoc ships as an Agent Skill, and the skill teaches the agent to run the CLI. It works with Claude Code, Codex, Cursor, OpenCode, and other compatible clients. This is the part I would actually go do today. Coding agents are strangely bad at office documents, and the usual workaround is either a Python dependency you install into the sandbox or copy-pasting the contents by hand. A skill that installs a fast binary and teaches the agent when to reach for it fixes a category of small daily friction.
One caution that has nothing to do with anydoc specifically: a skill install writes into your agent's config directory, and that directory is now an active target. Microsoft disclosed a self-propagating npm worm this week whose persistence step writes into .claude/settings.json. Diff your .claude/ directory after any skill install, from any source. The habit costs seconds and this is a good week to start it.
For a real pipeline, the Node and Python bindings are the shape you want. Node conversion runs on the libuv thread pool and never blocks the event loop. Python releases the GIL. Both ship types or stubs. The API is three functions in every language: to_markdown from a path, to_markdown_bytes from bytes with content detection, and to_document when you want the model itself, which carries embedded assets tagged with their media type.
That third one is underrated. If you are building retrieval, stopping at the document model rather than the Markdown gives you structure to chunk on instead of a string to regex over.
There is also a WebAssembly build, and the demo page runs it locally so files never leave the machine. For anyone whose blocker on document ingestion is a compliance conversation rather than a technical one, in-browser conversion is a different answer to the same question.
Where I would hesitate
The repo has 100 commits and no tagged releases showing on the page, which for a project with 5.2k stars means the audience arrived well ahead of the version history. The testing story is better than that sounds, with a committed fixture corpus under snapshot test, mutation testing across every fixture in tests/robustness.rs, and cargo-fuzz targets per format. Still, this is young code parsing hostile binary formats, and parsers of hostile binary formats are where memory-safety bugs historically live. Rust removes one class of those and not the class where a malformed file eats your RAM. The ResourceLimit variant says the authors thought about it; I would still run untrusted documents through it in a sandbox.
The dependency picture is worth checking yourself too. "No external services" and "no ML models" are both true, and neither means zero crates in Cargo.lock. Read the lockfile before you commit this to a regulated pipeline.
And the honest comparison for most teams is not anydoc against markitdown. It is anydoc against the hosted parsing API you already pay for, which handles scans and gives you a support contract. anydoc wins on cost, latency, and data residency. It loses on the scanned pile, and if that pile is 40% of your volume, the calculus flips.
Run it against your own corpus. That is the only benchmark whose corpus you can actually redistribute to yourself, and it will take you an afternoon.
Sources: firecrawl/anydoc on GitHub, anydoc benchmark harness, anydoc Agent Skill, firecrawl/pdf-inspector, Firecrawl Parse, in-browser demo.