Why You Should Care About anydoc
When feeding documents to LLMs, conversion quality directly impacts RAG accuracy. Misaligned tables from Word, lost hierarchy from PPT, broken paragraphs from PDF — every formatting issue degrades vector search precision. Previously, stitching together pandoc, python-docx, LibreOffice, and more was the only way to cover mainstream office formats — costly to maintain and inconsistent in output.
anydoc is a document conversion library built from scratch in Rust by the Firecrawl team. One API call handles 14 formats with a median conversion time of just 4.4 milliseconds. It has already earned 12,000+ stars on GitHub and is becoming the new standard for document preprocessing in RAG/LLM toolchains.
The 14 Supported Formats
anydoc boasts the broadest format coverage of any open-source conversion tool:
| Category | Extensions |
|---|---|
| Word | .doc, .docx, .docm |
| PowerPoint | .ppt, .pps, .pot, .pptx, .pptm, .ppsx, .ppsm |
| Excel | .xls, .xlsx, .xlsm, .xlsb |
| OpenDocument | .odt, .ods, .odp |
| Rich Text | .rtf |
| eBook | .epub |
| Data | .csv |
.pdf |
Notably, both .doc (legacy binary) and .docx (modern XML) are supported — a rarity among similar tools. Most only handle .docx and choke on older .doc files.
Benchmark: The Secret Behind 4.4ms
The official benchmark tested 7 tools on 100 real-world documents across all 14 formats:
| Tool | Formats | Median Time | Quality Score | Completeness | Structure | Formatting | Cleanliness |
|---|---|---|---|---|---|---|---|
| anydoc | 14/14 | 4.4ms | 81 | 87 | 79 | 78 | 81 |
| libreoffice | 12/14 | 1129.5ms | 40 | 59 | 42 | 40 | 24 |
| unstructured | 8/14 | 572.9ms | 63 | 76 | 59 | 51 | 63 |
| markitdown | 6/14 | 134.8ms | 65 | 78 | 66 | 60 | 52 |
| pandoc | 5/14 | 102.1ms | 56 | 74 | 57 | 56 | 38 |
| docling | 4/14 | 513.6ms | 57 | 60 | 60 | 57 | 51 |
| mammoth | 1/14 | 52.5ms | 70 | 84 | 71 | 75 | 51 |
Key findings:
- Speed dominance: anydoc's 4.4ms is 23x faster than runner-up pandoc (102.1ms) and 257x faster than LibreOffice (1129.5ms)
- Highest quality: Top scores on every judged format
- Broadest coverage: The only tool supporting all 14 formats
- Rust advantage: Pure Rust, no ML models, no external services
Quality was scored by Claude Sonnet 5 as an LLM judge, blind-comparing outputs against ground truth (first 6 pages rendered by LibreOffice). Each pair was judged twice with swapped positions to eliminate bias — 482 verdicts total.
Installation & Quick Start
anydoc ships a Rust core with Node.js, Python, and WebAssembly bindings, plus a zero-config CLI.
CLI (Zero Install)
# Run directly with npx; prebuilt binary downloads on first run
npx @firecrawl/anydoc report.docx # Markdown to stdout
npx @firecrawl/anydoc slides.pptx -o slides.md # Output to file
npx @firecrawl/anydoc - --format csv < data.csv # Read from stdin
Python Integration
pip install firecrawl-anydoc
import anydoc
# Convert from file path
markdown = anydoc.to_markdown("report.docx")
# Convert from bytes (auto-detect format)
with open("report.docx", "rb") as f:
data = f.read()
markdown = anydoc.to_markdown_bytes(data)
# Specify format (needed for signature-less formats like CSV)
markdown = anydoc.to_markdown_bytes(csv_data, "csv")
# Get document model (includes embedded assets like images)
document = anydoc.to_document(data)
Node.js Integration
npm install @firecrawl/anydoc
import { toMarkdown, toMarkdownBytes, toDocument } from '@firecrawl/anydoc';
const md = await toMarkdown('report.docx');
const mdFromBytes = await toMarkdownBytes(bytes);
const doc = await toDocument(bytes);
Rust Native
cargo add anydoc
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)?;
Integration with LangChain / LlamaIndex
In a RAG pipeline, anydoc typically serves as the document loading step, converting various formats to Markdown before chunking and vectorization.
With LangChain
import anydoc
from langchain.text_splitter import MarkdownTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
def load_documents(file_paths):
docs = []
for path in file_paths:
md = anydoc.to_markdown(path)
docs.append({"content": md, "source": path})
return docs
splitter = MarkdownTextSplitter(chunk_size=1000, chunk_overlap=100)
raw_docs = load_documents(["report.docx", "slides.pptx", "data.xlsx"])
chunks = []
for doc in raw_docs:
chunks.extend(splitter.split_text(doc["content"]))
embeddings = OpenAIEmbeddings()
db = FAISS.from_texts(chunks, embeddings)
db.save_local("./faiss_index")
With LlamaIndex
import anydoc
from llama_index.core import Document, VectorStoreIndex
file_paths = ["contract.docx", "presentation.pptx", "budget.xlsx"]
documents = []
for path in file_paths:
md = anydoc.to_markdown(path)
documents.append(Document(text=md, metadata={"source": path}))
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("What are the breach clauses in the contract?")
print(response)
Best Practices for RAG
1. Use to_document() for Embedded Resources
When documents contain images, to_document() preserves image bytes and media types. You can extract alt text, send images to a multimodal model for OCR, or preserve external URL references.
2. Leverage Auto Format Detection
anydoc detects format from file content magic bytes, not extensions. Even misnamed files convert correctly:
fmt = anydoc.format_from_bytes(data) # Returns Format enum
3. Batch Processing with Threads
Python bindings release the GIL, so multi-threaded batch conversion runs without blocking:
import concurrent.futures
import anydoc
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
results = list(executor.map(anydoc.to_markdown, file_paths))
4. Handling Scanned PDFs
anydoc embeds pdf-inspector to auto-detect text vs. scanned PDFs. For scanned documents, pair with Firecrawl's Parse API for OCR.
Limitations
- No OCR built-in: Scanned PDFs need external OCR. Firecrawl Parse API fills this gap.
- Complex layouts may differ: Multi-column or heavily mixed content may not match professional typesetting. anydoc targets LLM-readable structured text, not pixel-perfect reproduction.
- Legacy .doc format: Supported but OLE2 binary parsing is inherently more complex than .docx.
- No ML enhancement: Deliberately avoids ML models — fast and GPU-free, but less capable on complex layouts than docling or marker.
- WASM memory limits: Browser version runs locally (great for privacy), but large files may hit browser memory constraints.
Verdict
anydoc solves a real pain point: replacing the four-or-five-library juggling act with one lightweight, fast, format-complete tool for document conversion in RAG/LLM pipelines.
Strengths: - 14 formats — the only open-source tool covering all of them - 4.4ms median — 23x faster than the next fastest - Top quality scores across every judged format - Pure Rust, zero external dependencies, no GPU needed - Python, Node.js, Rust, and WASM bindings
Best for: - RAG pipeline builders handling mixed user uploads - Teams needing consistent document output - Real-time applications with latency requirements - Developers tired of maintaining multiple conversion dependencies
GitHub: firecrawl/anydoc (12,000+ stars)
FAQ
Q1: How does anydoc differ from pandoc? pandoc is a general-purpose converter supporting many input/output formats but with limited depth per format. anydoc focuses on converting office documents to LLM-friendly Markdown — higher quality on its 14 formats, 23x faster, and purpose-built for RAG.
Q2: Does anydoc require a GPU or external service? No. Pure Rust, no ML models, no external APIs. Runs locally with a 4.4ms median per document.
Q3: Can anydoc handle scanned PDFs? anydoc embeds pdf-inspector for text-based PDFs. For scanned (image-based) PDFs, pair with Firecrawl Parse API (with OCR).
Q4: What output format does anydoc produce? GitHub Flavored Markdown (GFM) — headings, tables, lists, code blocks, links. All 14 formats produce consistent output.
Q5: Can anydoc run in the browser? Yes. The WebAssembly build (@firecrawl/anydoc-wasm) converts files locally without uploading — ideal for privacy-sensitive scenarios.