Firecrawl anydoc converts 14 office formats to Markdown in 4.4ms

anydoc is a pure Rust document parser from Firecrawl that turns Word, Excel, PowerPoint, PDF, and ten other formats into consistent GitHub-Flavored Markdown. Median conversion is 4.4ms, MIT licensed, with Rust, Node, Python, WASM, and CLI bindings built for agent pipelines.

SaifullahSaifullah
8 min read
Firecrawl anydoc converts 14 office formats to Markdown in 4.4ms

Your agent just received a .docx, a .pptx, and a legacy .xls from a client upload folder. The RAG chunker expects Markdown. Spinning up LibreOffice headless for each file adds seconds of latency and a fragile subprocess layer.

Firecrawl anydoc is a different bet: pure Rust, no ML, MIT license, and a median 4.4ms per document on Firecrawl's published benchmark. One digest I read claimed 500 Word docs in 1.7 seconds. Whether you hit that exact number depends on hardware and batch warmth, but the order of magnitude is the point. Document ingestion stops being a bottleneck and starts behaving like a library call.

What anydoc actually does

anydoc reads document bytes, detects the format from content markers (not just the file extension), parses into a shared document model, and renders GitHub-Flavored Markdown through one serializer.

That single output path matters for agents. Headings, tables, footnotes, task lists, and math blocks behave the same whether the source was a .doc from 2003 or a .pptx from last week. You do not maintain fourteen different post-processors.

Firecrawl built it to power Firecrawl Parse, their hosted document API. Self-host the library when you want the same conversion inside your VPC, a CI job, or a local agent skill with zero network round trips.

Pipeline diagram showing mixed office documents flowing through anydoc format detection, shared document model, and GFM Markdown output for agent ingestion

Fourteen formats, one Markdown dialect

Format familyExtensionsNotes
Word.doc, .docx, .docmLegacy OLE and modern OOXML
PowerPoint.ppt, .pps, .pot, .pptx, .pptm, .ppsx, .ppsmSpeaker notes preserved
Excel.xls, .xlsx, .xlsm, .xlsbTables with merged cells
OpenDocument.odt, .ods, .odpLibreOffice-native packages
Rich Text.rtfEquations to LaTeX
EPUB.epubMathML to GFM math
CSV.csvNeeds extension or explicit format hint
PDF.pdfText-based only via pdf-inspector

Content-based detection reads PDF headers, RTF open groups, OLE stream names, and ZIP package mimetypes. Mislabeled files still convert when the bytes match a known signature. CSV has no magic bytes, so name it explicitly when you pass raw bytes.

Structure the library preserves includes headings with anchors, bold and italic, strikethrough, inline and fenced code, links and internal cross-references, nested and task lists with source numbering, tables with header rows and merged cells, block quotes, footnotes, endnotes, and speaker notes. Equations from Word OMML, PowerPoint OMML, OpenDocument MathML, and RTF convert to $...$ inline and $$ block math. Embedded images surface as alt text in Markdown while raw bytes stay on the document model for downstream asset handling.

How it fits agent pipelines

Most document-to-LLM stacks look like this today:

upload → guess format → subprocess or cloud API → messy HTML or plain text → cleanup regex → chunk → embed

anydoc collapses the middle:

upload → anydoc::to_markdown → chunk → embed

For coding agents, Firecrawl ships an Agent Skill you install with one command:

npx skills add firecrawl/anydoc

After that, Claude Code, Cursor, Codex, or any skills-compatible agent can convert attachments before summarization or code generation. The skill teaches the CLI path so the agent does not guess flags.

CLI usage is equally direct:

npx @firecrawl/anydoc report.docx npx @firecrawl/anydoc slides.pptx -o slides.md npx @firecrawl/anydoc - --format csv < data.csv

npx pulls a prebuilt binary on first run. Install globally with npm install -g @firecrawl/anydoc if you want a permanent anydoc on PATH.

Bindings that stay out of the way

SurfaceInstallTypical call
Rustcargo add anydocanydoc::to_markdown("report.docx")?
Node.jsnpm install @firecrawl/anydocawait toMarkdown('report.docx')
Pythonpip install firecrawl-anydocanydoc.to_markdown("report.docx")
Browser WASMnpm install @firecrawl/anydoc-wasmtoMarkdownBytes(bytes) after init()
CLInpx @firecrawl/anydocstdout Markdown or -o file.md

Node runs conversion on the libuv thread pool so the event loop stays responsive. Python releases the GIL during native work. WASM powers the browser demo where files never leave the machine.

Rust example for bytes with explicit format when needed:

use anydoc::{self, Format}; let markdown = anydoc::to_markdown("report.docx")?; let from_bytes = anydoc::to_markdown_bytes(&bytes, None)?; let from_csv = anydoc::to_markdown_bytes(&bytes, Format::Csv)?; let document = anydoc::to_document(&bytes, None)?;

Stopping at to_document is useful when you need embedded asset bytes tagged by media type, not just the Markdown string.

anydoc vs MarkItDown vs LibreOffice (brief)

Firecrawl published a head-to-head benchmark on 100 real-world documents. An LLM judge (Claude Sonnet) scored blind outputs against ground-truth page renders. Speed is median milliseconds per warm conversion on a Ryzen 9 9950X3D.

ToolFormats coveredMedian msOverall score
anydoc14/144.481
LibreOffice12/141129.540
MarkItDown6/14134.865
unstructured8/14572.963
pandoc5/14102.156

MarkItDown ( Microsoft MarkItDown ) is the tool many Python teams reach for first. It covers six of the fourteen formats in this comparison, scores reasonably on those, and runs at roughly 135ms median. Fine for occasional .docx in a notebook. Less fine when your ingestion queue mixes .ppt, .xlsb, and .odp every hour.

LibreOffice headless still appears in production pipelines because it converts almost anything if you wait long enough. Median 1129.5ms and a 40 quality score in this harness tell you why ops teams complain. Subprocess management, font dependencies, and zombie soffice processes are real costs the spreadsheet row does not capture.

anydoc's pitch is narrower and sharper: all fourteen formats, highest per-format scores in the published table, and two orders of magnitude faster than LibreOffice on median latency. No ML means predictable CPU use and no GPU queue.

Comparison chart of document converters showing anydoc at 4.4ms median versus MarkItDown at 134.8ms and LibreOffice at 1129.5ms

Errors you should plan for

anydoc returns Err only when no meaningful Markdown can be extracted. Variants map cleanly across Rust, Node, Python, and WASM:

ErrorMeaningAgent handling
EncryptedPassword-protected fileAsk user for decrypted copy
UnsupportedUnknown format or image-only PDFRoute to OCR or reject
MalformedStructurally unusableLog and skip
ResourceLimitSafety cap on decompression or nestingSplit or quarantine file
MissingPartRequired package part absentTreat as corrupt upload

Pattern for batch ingestion in Rust:

match anydoc::to_markdown(path) { Ok(markdown) => Some(markdown), Err(error @ (anydoc::ConvertError::Encrypted | anydoc::ConvertError::Unsupported(_))) => { unconverted.push((path, error)); None } Err(error) => return Err(error), }

Encrypted and unsupported files get recorded; unexpected I/O failures abort the batch.

Where anydoc is not enough

Scanned PDFs with no text layer still need OCR. anydoc handles text-based PDFs locally through pdf-inspector. Image-only pages return Unsupported. If your mailbox receives fax scans daily, pair anydoc with an OCR stage or use hosted Firecrawl Parse for the full stack.

Schema-constrained field extraction is also out of scope. anydoc gives you clean Markdown for chunking and summarization, not typed JSON against your invoice schema. For that layer, tools like structured extraction models or schema-decoding pipelines still belong downstream.

Practical wiring for Applied AI shipping

When I evaluate document ingestion for a client RAG or agent project, anydoc checks four boxes that matter in production:

  1. Latency at scale. Sub-10ms median per file means ingestion keeps up with webhook bursts. The 500 docs in 1.7s figure is the kind of throughput that makes same-minute indexing realistic on a single core.

  2. Format breadth without forked code paths. One serializer means your chunker and metadata extractors see consistent heading levels and table syntax across the whole office suite.

  3. Deploy anywhere. Rust crate for services, Python for notebooks, WASM for browser-side privacy, CLI for agent skills. MIT license removes procurement friction.

  4. No hidden ML bill. CPU-only conversion makes cost predictable on Workers, Lambda, or a bare VM. You pay for compute you can measure.

My default pattern: anydoc at the edge of the pipeline for native office files, OCR only for scanned PDFs, then your existing chunker and embedder. Keep LibreOffice as a fallback only if you still have exotic legacy macros that must render visually (rare in agent workflows).

Try it before you commit

  • Browse the WASM demo locally: firecrawl.github.io/anydoc
  • Read the benchmark harness in the repo under bench/
  • Install the agent skill and drop a .docx into your next Cursor session

Document parsing used to be the slow, subprocess-heavy step agents worked around. anydoc treats it like parsing JSON: fast, deterministic, and boring in the best way.

If you are wiring mixed-format ingestion into an agent or RAG pipeline and want a second pair of eyes on chunk boundaries and failure handling, book a free discovery call.

Share this post

Related posts