firecrawl/anydoc: One Markdown Serializer for Word, PowerPoint, Excel, EPUB and PDF
Convert Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, and PDF to clean Markdown. Built in Rust, with Node.js and Python bindings.
At a glance
- What is it?
- anydoc is a Rust conversion library with Node.js, Python, browser and CLI front ends that turns office documents into GitHub-Flavored Markdown through a shared document model. It is fast and consistent, but it does not OCR, and the Rust crate never makes network calls.
- Who is it for?
- Adopt anydoc if your pipeline already produces text-based office files and you want one Markdown dialect across docx, pptx, xlsx, odt, rtf, epub, csv and PDF, especially if you are working in Rust or want the WebAssembly build so files never leave the browser. Skip it if your inputs are scans: the library does no OCR, and the hosted OCR path sends the whole document to Firecrawl Parse.
- 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 19 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 September 16, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem anydoc solves: eight formats, one Markdown dialect
Most conversion stacks grow one parser per format. A docx library here, a PDF text extractor there, a spreadsheet reader bolted on later. Each produces slightly different Markdown: different escaping, different table syntax, different handling of code blocks. If the output feeds an LLM or a static site generator, those differences become bugs you fix repeatedly.
anydoc takes the opposite route. Every format parses into a shared document model, and a single Markdown serializer renders that model. The README states the goal plainly: escaping, tables, heading anchors and footnotes behave identically whether the input was a .doc from 2003 or a .pptx from yesterday. That is the whole pitch. It is a library for people who need predictable Markdown, not a document viewer and not a full office suite.
The audience is narrow but real: backend engineers building ingestion pipelines, RAG systems, or documentation tooling, and anyone who wants conversion available from Rust, Node.js, Python or the browser without maintaining four separate code paths. Firecrawl built it to power Firecrawl Parse, so the conversion layer has a production consumer behind it.
How the shared document model works
The architecture is visible in the repository layout and the dependency list. Format-specific parsers live in src/, each one producing the same intermediate document model. A single Markdown renderer walks that model. The Cargo.toml dependencies tell you which parsers exist: cfb for OLE compound files, quick-xml for XML-based formats, zip for OOXML and ODF packages, csv for delimited text, pdf-inspector for PDF, encoding_rs for character decoding, and flate2 for decompression.
Format detection is content-based, not extension-based. The README says the format is read from the bytes themselves: a PDF header, an RTF open group, OLE stream names, a ZIP package mimetype. That matters because a .docx renamed to .zip, or a CSV saved as .txt, still converts. Signature-less formats are the exception: CSV carries no magic bytes, so the API lets you name the format explicitly.
The document model is also the escape hatch. Instead of stopping at Markdown, you can call to_document and get the model itself, which carries embedded assets. Images and embedded objects render as alt text in the Markdown, but the raw bytes stay on the model, tagged with their media type. If you need to upload images to object storage and rewrite links, that is the layer to do it in. The README does not document a hook for rewriting image links during Markdown rendering, so plan on the two-pass approach.
Installing anydoc and converting your first document
The fastest path is the CLI through npx, which downloads a prebuilt binary for your platform on first run. This command converts a Word file and prints Markdown to stdout:
npx @firecrawl/anydoc report.docxAdd -o to write to a file instead, or install globally if you want a permanent anydoc command:
npx @firecrawl/anydoc slides.pptx -o slides.md
npm install -g @firecrawl/anydocThe CLI reads stdin when you pass a single dash, which is useful for CSV because the format cannot be detected from content. You have to name it:
npx @firecrawl/anydoc - --format csv < data.csvFrom Python, install the package and call to_markdown on a path. The second argument opts into hosted OCR for scanned pages:
import anydoc
markdown = anydoc.to_markdown("report.docx")
markdown = anydoc.to_markdown("scan.pdf", ocr="hosted")
document = anydoc.to_document(data)From Rust, add the crate and call the same functions. Note that the byte-oriented function takes an optional format, which is how you handle CSV:
let markdown = anydoc::to_markdown("report.docx")?;
let markdown = anydoc::to_markdown_bytes(&bytes, None)?;
let markdown = anydoc::to_markdown_bytes(&bytes, anydoc::Format::Csv)?;
let document = anydoc::to_document(&bytes, None)?;What you should see is GitHub-Flavored Markdown with headings, tables, lists and footnotes. The README does not show sample output, so verify the shapes you care about against your own files rather than trusting a screenshot.
The OCR boundary is the design decision that matters most
anydoc reads text-based PDFs locally. It does no OCR. A PDF with scanned or image-only pages fails with NeedsOcr rather than returning empty output, which is the right failure mode: you get an error instead of silently losing content.
Opting in changes where the document goes. The CLI flag is --ocr hosted, the Node option is { ocr: 'hosted' }, and the Python argument is ocr="hosted". Those documents go to Firecrawl Parse, which OCRs them and returns Markdown in the same shape. No signup is needed to try it; setting FIRECRAWL_API_KEY raises the limits.
Two constraints are worth stating before you build on this. First, only documents that need OCR leave the machine, but the whole document goes, because Parse has no page selection. A 400-page PDF with three scanned pages uploads all 400. Second, the Rust crate has no ocr option at all and never makes network calls. If your service is written in Rust and you need OCR, you have to put the hosted call somewhere else in your stack.
Failures from the hosted path are typed: Node rejects with code: 'hosted' and Python raises HostedError. The README does not document a retry policy or a timeout, so treat those as your responsibility. For teams with strict data residency rules, the local-only path is the honest default, and the hosted path is an explicit opt-in you can audit.
Equations, assets and the formats anydoc does not promise
Equation handling is more thorough than most converters. Word and PowerPoint OMML, OpenDocument and EPUB MathML, and RTF equations all convert to GitHub-flavored math: $...$ inline and $$ blocks. If your corpus is academic or technical, that saves a preprocessing step.
Structure support covers headings with anchors, bold, italic, strikethrough, inline code and code blocks, links and internal cross-references, bulleted, numbered, nested and task lists using the source's own numbering, tables with merged cells and header rows, block quotes, footnotes and endnotes, and speaker notes. That last one is unusual and useful for slide decks, where speaker notes often carry the actual argument.
The limitations are the usual ones for this class of tool. There is no OCR in the library. There is no documented rollback or dry-run mode for the CLI. The README does not describe how legacy binary .doc parsing handles malformed OLE streams, and fuzz/ exists in the repository, which suggests the maintainers treat parser robustness as ongoing work rather than a solved problem. There is also no documented plugin or extension point for custom Markdown output, so if you need a dialect other than GitHub-Flavored Markdown, you are modifying the serializer or post-processing its output.
When to use a different tool
The closest alternative is Pandoc. The difference in approach is structural: Pandoc is a document converter built around an AST with readers and writers for dozens of formats, and it runs as a Haskell binary you shell out to. anydoc is a library first, with bindings, and its writer count is one. If you need to emit reStructuredText, AsciiDoc, DocBook or LaTeX, Pandoc does that and anydoc does not.
If your inputs are mostly scanned PDFs, the right tool is an OCR pipeline, not a converter. anydoc's hosted path delegates to Firecrawl Parse for exactly this reason, and if you are already running Tesseract or a cloud OCR service, keep it.
For HTML-to-Markdown, neither tool is the answer; that is a different problem. And if you need a spreadsheet engine that evaluates formulas, anydoc converts the file to Markdown, it does not compute anything. The README describes a converter, and reading it as a document processing platform will lead to disappointment.
Maintenance, licensing and the cost of upgrading
The repository is not archived. The last push was on 2026-08-28, and the most recent release, v0.2.4, is titled Scanned pages and landed on 2026-08-27. Two releases before it, v0.2.3 and v0.2.2, both landed on 2026-08-20, which suggests a burst of activity rather than a steady cadence. The version is 0.2.x, so treat the API as pre-1.0 and read the changelog before upgrading.
The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are included. That is the standard reading, not legal advice; if you are embedding this in a product with unusual distribution terms, have counsel review it. The hosted OCR path is a separate service with its own terms, and the README links to Firecrawl Parse rather than describing its pricing or data handling.
Upgrade cost is mostly dependency-driven. The crate pins zip at 8.6.0 with default features off, quick-xml at 0.41.0, and pdf-inspector at 1.14.2. The workspace includes node/, python/ and wasm/ members, so a change to the core model can ripple into three binding surfaces at once. The rust-version is 1.88, raised from 1.85 by the zip dependency, which is worth checking against your toolchain before you commit.
Editorial conclusion
Adopt anydoc if your pipeline already produces text-based office files and you want one Markdown dialect across docx, pptx, xlsx, odt, rtf, epub, csv and PDF, especially if you are working in Rust or want the WebAssembly build so files never leave the browser. Skip it if your inputs are scans: the library does no OCR, and the hosted OCR path sends the whole document to Firecrawl Parse. Before committing, convert a representative sample of your own files and check the parts the README does not promise, such as table merging and footnote placement.
Frequently asked questions
What is anydoc?
anydoc is a Rust library that converts Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV and PDF files into GitHub-Flavored Markdown. It ships Node.js, Python, browser (WebAssembly) and CLI front ends, and each format parses into a shared document model rendered by one Markdown serializer.
Is anydoc translator safe?
The library converts text-based files locally and the Rust crate never makes network calls. Scanned PDFs fail with NeedsOcr unless you opt into hosted OCR, in which case the whole document goes to Firecrawl Parse, since Parse has no page selection.
What is anydoc translator?
The README does not describe a product called anydoc translator. It documents a document-to-Markdown conversion library and a browser demo that runs it as WebAssembly, plus an Agent Skill that teaches coding agents to call the anydoc CLI.
Community notes