`anydoc`: The Rust Parser That Turns Office Files Into One Clean Markdown Path

Firecrawl’s document engine treats messy formats as a routing problem, not a monolith. That design makes it fast, secure, and unusually elegant for LLM pipelines.

8 min read View on GitHub More from firecrawl

A wide rail-yard switchboard where different file carriages labeled DOCX, XLS, RTF, ZIP, and PDF arrive at a central routing switch. Most paths converge into a shared document chamber, while the PDF carriage takes a separate bypass track toward Markdown output. The image explains that anydoc is a decision engine first and a converter second.
anydoc begins by routing each file to the right path, then normalizes only where the format supports it.
Key Takeaways

A Word file, an Excel sheet, a legacy `.doc`, and a ZIP-based OOXML package are all just bytes at the door. `anydoc` starts by asking a smaller question than most converters: what is this thing, really? That one choice explains most of the design.

The architecture is legible because the routing layer comes first and the renderer comes last.

A document parser that thinks in routes, not formats

Most document tools start with a format and hope everything else follows. `anydoc` starts with detection, then chooses the smallest viable parser. That is why a mislabeled file, a zipped office package, and a binary legacy blob do not all trigger the same machinery.

No single library reliably converts every common document format to clean markdown. You end up stitching four or five tools together, each with its own dependencies, output shape, and failure modes. So we built two Firecrawl projects to solve it: pdf-inspector for PDFs, and AnyDoc for everything else.

The Universal Document Model is the real product

The key abstraction is not Markdown. It is the internal document model made of Blocks and Inlines. `anydoc` converts many office formats into that shared representation first, then renders once, consistently, at the end.

// Conceptual shape of the pipeline
let bytes = input.read_all()?;
let format = detect::from_bytes(&bytes)?;

match format {
    Format::Docx | Format::Xls | Format::Rtf | Format::Odt => {
        let doc = parse_to_document_model(&bytes)?;
        markdown::render(&doc)
    }
    Format::Pdf => pdf_inspector::render_markdown(&bytes),
    other => Err(ConvertError::Unsupported(other)),
}

That split matters. A table extracted from Excel and a table extracted from Word can land in the same Markdown shape because they were normalized through the same IR. You do not get five different downstream behaviors for five upstream file families.

A close-up view of a battered office filing cabinet being emptied into a set of clean stacked blocks and inline elements. On the left, torn XML shards, paper clips, and legacy file fragments spill from drawers. On the right, those fragments resolve into neat blocks flowing toward Markdown lines, while a small side chute marked by visual metaphor only carries PDFs around the main stack. The image explains how normalization turns chaotic source structures into one shared output model.
Normalization is the real compression step. Different source files become one internal shape before Markdown is written.

Why PDFs take a different road

This is the architectural tell. PDFs do not get forced through the same semantic funnel, because they are often positional documents rather than cleanly structured ones. `anydoc` treats that as a feature, not a failure.

PathWhat it optimizes forModel strategyPDF handlingBest fit
Office formatsFast Markdown from structured document internalsNormalize into Blocks and InlinesSeparate parser path when neededLLM ingestion for Word, Excel, RTF, and OOXML
PDFsText-layer extraction without pretending semantics are perfectBypass the shared model when structure is too weakDirect Markdown path via pdf-inspectorLocal parsing of readable PDFs
General-purpose convertersCoverage across many file kindsOften broad and genericUsually one more case in a large pipelineMixed conversion tasks, not one narrow ingestion layer

That choice keeps the system honest. It is better to admit that PDF is different than to squeeze it into a model that looks tidy but loses meaning.

Built for untrusted documents

Office files are an attack surface. `anydoc` answers that with Rust, package inspection, XML hardening, depth and node limits, and fuzzing across formats. The security story is not an add-on. It is part of the ingestion contract.

The package layer matters here. ZIP and OLE containers are inspected before their contents are trusted, which is exactly what you want when files come from crawls, uploads, or unknown sources.

Why this feels faster than the Python stack

The speed story is not just Rust versus Python. It is fewer conversions, fewer heavyweight abstractions, streaming XML parsing, one serializer, and no model inference by default. If the file already contains text and structure, `anydoc` reads that structure directly.

processed 500 DOCX files in 1.7 seconds; free to use.

Nick Camara, Co-founder, Firecrawl · The Neuron - August 6, 2026

What `anydoc` is really competing with

The real comparison is not feature count. It is abstraction choice. `anydoc` is a structural compiler for Markdown generation, while many alternatives are broader, heavier, or optimized for different trade-offs.

ProjectPrimary goalLanguage/runtimeModel strategyPDF handlingBest use case
anydocFast LLM-ready Markdown from office filesRust with Node and Python bindingsShared document model plus PDF bypassDedicated PDF path via pdf-inspectorLocal ingestion pipelines
MarkItDownGeneral document to Markdown conversionPythonMostly direct conversionDepends on surrounding stackConvenient broad conversion
UnstructuredFlexible document partitioningPythonHeavier partitioning pipelineOften more complexEnterprise RAG preprocessing
DoclingHigh-quality document parsingPython and ML-assisted componentsLayout-aware extractionStronger compute footprintDocument intelligence workflows
PandocGeneral document transformationHaskell and CLI ecosystemFormat translation toolchainBroad but genericMulti-format conversion
Mammoth.docx to HTML or MarkdownJavaScript and browser-friendlySpecialized Word mappingNot a general PDF toolBest-in-class Word-only conversion

That is why `anydoc` lands as a systems tool, not a convenience wrapper. It narrows the problem until the abstraction is sharp enough to be useful.

The right abstraction for the AI ingestion layer

For AI pipelines, document conversion is becoming a narrow systems problem. Detect the file, parse what can be parsed, normalize structure when it is real, and bypass the model when the source format resists it. `anydoc` is interesting because it makes that sequence legible.

That is a better thesis than speed alone. Fast is useful, but a fast wrong abstraction ages badly. A small routing layer, a shared IR, and an explicit PDF exception feel like the kind of design that can survive contact with real documents.