本文以英文发布。
anydoc: Converting 14 Document Formats to Markdown in Milliseconds
A look at Firecrawl's Rust-based anydoc: how fast it converts docx, pptx, xlsx and PDF to Markdown on a fresh test set, what fails, and how to use it.
Firecrawl has open-sourced a document converter called anydoc, and the numbers attached to it are striking. Converting a docx through LibreOffice before handing it to a model can take over a second per file, without counting any manual steps. anydoc brings the median down to 4.4 milliseconds across 14 common formats, and it always emits one consistent flavor of GitHub-style Markdown.
What it is
This isn't another "good enough" converter. The whole pipeline was rewritten in pure Rust. Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV and PDF all map onto one shared document model, and one serializer writes the output. A legacy .doc from 2003 and a .pptx exported yesterday therefore produce the same look: heading levels, merged cells, footnotes and task lists behave identically. It runs locally, calls no external service and uses no machine learning models.
A common belief says document parsing needs a chain of stitched-together tools or a trip to the cloud. This speed and quality are reachable on your own machine. anydoc already powers Firecrawl's own Parse endpoint, where only scanned PDFs take an extra OCR step. Text-based PDFs and office files are handled locally in a single pass.
Checking the claim on a fresh corpus
The vendor's benchmark can't be reproduced. The README says its 100 files can't be redistributed and aren't in the repository. So a new test set was assembled: 40 files per format, each under 25 MB.
That made 206 files, of which 194 converted successfully.
The headline claim of a sub-5 ms median holds only for csv and xlsx. The docx median was 6.4 ms, and pptx and pdf each came in at 22. The slowest pptx took 346 ms, a big file in a sample that included items over two hundred megabytes. The 5 ms figure is a median over the entire mixed corpus, and line-oriented formats like csv pull it down.
In practical terms, a thousand docx files take about 6 seconds, while a thousand PowerPoint files take more than twenty. Both are quick, but plan around the slower numbers.
Eight of the twelve failures weren't the tool's fault. Those eight files reported malformed, so each was inspected with the file command. Two .docx files turned out to be one data and one JSON data, while two .xlsx and four .pptx files were all plain data. None were genuine Office documents. anydoc reads content by format and not by extension, so it wasn't fooled. With the eight fakes removed, the real success rate is 194 of 198, or 98%.
There's an unplanned bonus here. Run it across a document library, collect everything that reports malformed, and the result is a ready-made list of corrupt or mislabeled files. It wasn't built for that, yet it works.
Why this matters for LLM pipelines
Hand a contract to a model and the first problem is knowing what format it really is. The extension says .docx, but the content may be a modified ZIP. CSV has no signature, so it has to be declared by hand. Change tools and the output rules change too: tables sometimes turn into lists, footnotes vanish, and nested list indentation breaks.
For comparison, LibreOffice's median is above 1100 milliseconds, and it doesn't cover all 14 formats. unstructured, markitdown and pandoc each handle only part of the range, with quality scores peaking near 65. On the same batch of 100 real documents, anydoc scored 81 overall, with 87 for completeness, 79 for structure, 78 for formatting and 81 for cleanliness. That's about 250 times faster.
It isn't just a lab figure. 500 docx files finish in 1.7 seconds, quick enough to sit directly in an agent loop. Before, you'd build an async queue and wait on callbacks. Now even synchronous calls feel instant.
Old .xls and .odp files with wrong filenames were identified from the file header anyway. CSV needs an explicit hint, since without a signature the tool otherwise skips it. Small details like that are worth knowing before you build on it.
How it works inside
Every file starts with content detection: PDFs by file header, RTF by groups, ZIP containers by mimetype. After identification, a dedicated parser fills a unified Document model that holds blocks, inlines, tables, footnotes and embedded resources. A single GitHub-Flavored Markdown serializer finishes the job.
That's why merged table cells, heading anchors, strikethrough, code blocks and speaker notes all follow the same rules. Images and objects turn into Markdown with alt text, while the raw bytes stay in the model for later retrieval.
PDFs go through pdf-inspector, which only handles files that have a text layer. A pure scan returns Unsupported instead of pretending to be read, and an encrypted file gives a clear error. That honesty is better than "best effort conversion" any day.
Because it's pure Rust, there's no Python interpreter overhead and no temp files on disk. The Node binding uses libuv's thread pool, so the event loop isn't blocked, and the Python binding releases the GIL. That's how a 4.4 ms median becomes possible.
Some readers will call a Rust parser overengineering. Others package it as an agent skill, so Claude or Cursor can turn a file into Markdown whenever one shows up. Either camp has a case.
Using it
The command line is the most direct route:
npx @firecrawl/anydoc report.docx
Output goes to the terminal. Add -o slides.md to write a file. To read CSV from stdin:
npx @firecrawl/anydoc - --format csv < data.csv
In Node:
import { toMarkdown, toMarkdownBytes } from '@firecrawl/anydoc';
const md = await toMarkdown('report.docx');
const mdFromBytes = await toMarkdownBytes(bytes);
const mdCsv = await toMarkdownBytes(bytes, 'csv');
The Python version is similar:
import anydoc
md = anydoc.to_markdown("report.docx")
md_bytes = anydoc.to_markdown_bytes(data)
md_csv = anydoc.to_markdown_bytes(data, "csv")
A browser build exists as @firecrawl/anydoc-wasm, so files never leave the machine. For Rust, run cargo add anydoc.
Agent setups are even simpler. Running npx skills add firecrawl/anydoc makes the toolchain call it automatically whenever a document turns up.
Expect clean Markdown with tables, lists and headings intact. Complex documents occasionally lose edge-case styling, which is a parser trade-off and not a crash.
Limits to plan for
The project is at the 0.1.x stage. Badly malformed files or oversized embedded objects could in theory raise ResourceLimit. In production, handle the returned errors by category, and log encrypted and unsupported files separately so one bad input can't take down the whole pipeline.
If conversion can happen locally, skip the cloud round trip, and save scanned documents for endpoints with OCR. Speed and quality are already good enough, so what remains is how it fits into your own pipeline. The project lives at github.com/firecrawl/anydoc.