Published August 17, 2026 · 9 minute read
The short version: AnyDoc is now our primary local parser for PDF, DOCX, PPTX, and XLSX. MarkItDown still handles the rest of our supported formats and takes over when a recoverable AnyDoc conversion fails or when an Office file contains images we need to package for download.
This is not a claim that AnyDoc is universally “better than MarkItDown.” They were designed for different jobs. For GetMarkdown’s no-install browser workflow, AnyDoc is the better primary engine for our four core document formats. MarkItDown remains the better compatibility tool for the broader set of inputs and behaviors our existing users rely on.
The problem with our MarkItDown-only browser pipeline
Microsoft’s MarkItDown is a Python utility with a useful conversion model: preserve the document structure that matters to language models—headings, lists, links, and tables—without trying to reproduce every visual detail. It supports Python and command-line use, optional format dependencies, plugins, Docker, and an MCP server. That flexibility is a major strength.
Browsers do not run Python natively, so our original implementation booted Pyodide, installed MarkItDown and its document dependencies, copied each upload into a virtual filesystem, ran the Python converter, then moved the result back into JavaScript. It kept processing local, but it also made a general-purpose Python runtime part of the hot path for every conversion.
The pipeline worked. The question was whether it was still the right default after a purpose-built browser package became available.
MarkItDown vs AnyDoc: the practical comparison
Firecrawl describes AnyDoc 0.1.9 as a Rust document parser with Node.js, Python, and browser WebAssembly bindings. Its Office parsers feed a shared document model and Markdown serializer; its PDF path uses a dedicated PDF parser. That shape fits a browser product differently from a Python-first utility.
| Question | AnyDoc 0.1.9 | MarkItDown |
|---|---|---|
| Natural runtime | Rust with a first-party browser WASM package | Python, CLI, Docker, library, and MCP |
| Our primary use | PDF, DOCX, PPTX, and XLSX in the browser | Compatibility formats and guarded fallback |
| Format breadth | Focused on document and ebook formats | Broader inputs, including HTML, images, audio, archives, and URLs |
| Embedded assets | Exposes asset bytes through its document model; Markdown uses alt text | Our established path extracts DOCX/PPTX images into a downloadable ZIP |
| OCR | Local package handles text-based PDFs, not scanned pages | Local built-ins are limited; optional plugins or Azure paths can add cloud-assisted capabilities |
| Best fit | A compact, browser-native document conversion core | Direct Python automation and a flexible conversion ecosystem |
The table summarizes the trade-offs that drove our migration, not every feature either project offers. See the dedicated AnyDoc vs Microsoft MarkItDown engine comparison for a more detailed side-by-side view.
The architecture before and after
Before
- 1. Validate the browser upload.
- 2. Start Pyodide and install Python packages.
- 3. Copy the file into a virtual filesystem.
- 4. Convert everything through MarkItDown.
- 5. Return Markdown or a Markdown-and-images ZIP.
After
- 1. Validate the browser upload.
- 2. Route PDF/DOCX/PPTX/XLSX to an AnyDoc worker.
- 3. Accept non-empty, successful Markdown.
- 4. Use MarkItDown for compatibility or recoverable fallback.
- 5. Preserve the same download contract.
The AnyDoc WebAssembly asset loads only when a routed document needs it. Conversion happens in a dedicated Web Worker, away from the main UI thread, and the idle worker is terminated after a short window to release its retained WASM memory. This keeps the converter responsive without changing the free tier’s local-processing model described on How GetMarkdown Works.
Why AnyDoc became the primary PDF and Office parser
The decisive feature was not a headline benchmark. It was the browser API. AnyDoc publishes a WASM build that accepts file bytes directly, returns Markdown directly, and exposes a structured document model for Office assets. It removes the Python virtual filesystem and package-installation layer from the normal PDF and Office path.
AnyDoc’s upstream project also publishes quality and speed results. We treat those as developer-reported benchmarks, not independent proof and not a promise about browser performance. Their corpus is not redistributable, and native timing on the project’s test machine is not the same as end-to-end conversion in a user’s browser. Our decision therefore rests on our architecture and regression checks, not on repeating a “milliseconds per document” claim.
Privacy remains straightforward: the free converter processes selected file bytes locally. WebAssembly changes the execution engine, not the data boundary. The page still makes ordinary network requests for application assets, the WASM module, and aggregate analytics; local conversion should not be confused with an offline application.
Why we kept MarkItDown instead of making a clean break
A migration is only useful if it does not quietly remove working behavior. MarkItDown remains in the pipeline for three concrete reasons:
- Format coverage. HTML, plain text, images, and audio remain on the existing compatibility route.
- Recoverable failures. If AnyDoc fails normally, cannot initialize, times out, or returns blank Markdown, we retry once with MarkItDown.
- Embedded-image downloads. AnyDoc’s document model exposes embedded assets, but its direct Markdown output represents them as alt text. For DOCX and PPTX files with embedded images, we deliberately use our existing extraction path so users still receive a ZIP containing the Markdown and image files.
That last behavior is easy to miss in a parser bake-off. A text-only comparison can look improved while the product’s download contract regresses. Preserving user-visible output mattered more than forcing every file through the new engine.
One error we intentionally do not fall back from
AnyDoc publishes a resourceLimit error when a document crosses fixed safeguards for decompression, nesting, or node count. Our router treats that error as terminal.
Retrying the same file through another engine could bypass the exact protection that stopped it. So ordinary parser failures get a compatibility attempt, while a resource-limit failure stops immediately for that file. Unrelated conversions in the same batch remain isolated and can continue. AnyDoc’s documented error model is what lets us distinguish the two cases.
Known limits in AnyDoc 0.1.9
The guarded rollout reduces detectable regressions; it cannot prove that every successful conversion is correct. Version 0.1.9 still requires review in several areas:
- Scanned and mixed PDFs. AnyDoc documents local support for text-based PDFs and says image-only PDFs are unsupported. A mixed PDF is harder: text pages can produce a successful result while scanned regions still need OCR. That means success alone is not an OCR-completeness signal.
- Presentation order. Slide boundaries, speaker notes, and the reading order of freely positioned shapes need source-by-source checking.
- Office semantics. Automatic Word numbering, spreadsheet display formats such as percentages, merged cells, and literal pipe characters inside tables can change how otherwise valid Markdown is interpreted.
- PDF text reconstruction. Character spacing, columns, headers, and tables can be wrong even when all expected words appear in the output.
These are not reasons to reject AnyDoc; they are reasons not to describe document conversion as solved. For a scanned PDF, dense table, or layout where omission has a high cost, use an OCR- and layout-aware workflow such as Advanced PDF, and compare the result with the original.
How we guarded against regressions
We tested the routing policy as a product contract, not only as a parser call. The migration includes checks for:
- AnyDoc-first routing for PDF, DOCX, PPTX, and XLSX.
- Unchanged MarkItDown routing for every other supported extension.
- One fallback after an ordinary AnyDoc error or empty result.
- No fallback after a resource-limit error.
- DOCX and PPTX embedded-image preservation.
- Worker initialization retry, request correlation, timeout recovery, and batch isolation.
- A real browser conversion that downloads Markdown from a representative PDF and confirms the emitted WASM asset is served correctly.
- Type checking, unit tests, browser tests, linting, and a production static build.
We will keep representative files in the test set as parser versions change. A 0.x dependency can move quickly, so we pinned AnyDoc to 0.1.9 rather than accepting an unreviewed compatible update.
So, is AnyDoc better than MarkItDown?
For a browser-first PDF and Office conversion core, AnyDoc is the better fit for GetMarkdown today. Its WASM package, byte-oriented API, document model, and explicit resource errors let us build a smaller and more isolated primary path.
For Python automation, a broad input catalog, plugins, MCP, or optional cloud integrations, MarkItDown remains compelling—and it remains part of our own product. The honest answer to “MarkItDown vs AnyDoc” is therefore architectural, not tribal: choose the engine that fits the runtime and preserve a fallback when real user files demand it.