MinerU and Docling go deep on documents. MarkItDown goes wide: one pip install, one command, and roughly any file on your disk comes out as Markdown.

The trade-off is stated plainly in its own README, which is refreshing — the output “may not be the best option for high-fidelity document conversions for human consumption.” It is built to feed text-analysis pipelines, not to reproduce your document. Mainstream models speak Markdown natively and it’s token-efficient, so that’s the target format.

What it eats

PDF · Word · PowerPoint · Excel · HTML · CSV, JSON, XML · EPub · images (EXIF plus OCR) · audio (EXIF plus speech transcription) · YouTube URLs · ZIP archives, iterating over the contents.

That ZIP line is the one people underrate: point it at an archive of mixed attachments and it walks the whole thing.

Install and run

Python 3.10+, in a virtualenv:

  pip install 'markitdown[all]'
markitdown report.pdf > report.md
markitdown report.pdf -o report.md
cat report.pdf | markitdown          # it reads stdin too
  

[all] pulls every optional dependency. If you know what you’re handling, install less:

ExtraFor
[pdf] [docx] [pptx] [xlsx] [xls]The obvious ones
[outlook]Outlook messages
[audio-transcription]Speech in wav and mp3
[youtube-transcription]Fetching YouTube transcripts
[az-doc-intel] [az-content-understanding]The two Azure backends below
  pip install 'markitdown[pdf, docx, pptx]'
  

When the built-in converters aren’t enough

Three escape hatches, in increasing order of cost:

Plugins, disabled by default — markitdown --list-plugins, then markitdown --use-plugins file.pdf. Search GitHub for the #markitdown-plugin hashtag. The first-party markitdown-ocr adds OCR to the PDF, DOCX, PPTX and XLSX converters by sending embedded images through an LLM vision model, reusing the same llm_client/llm_model pair MarkItDown already takes for image descriptions. No new binary dependencies. Note the failure mode: with no llm_client, the plugin still loads and OCR is silently skipped — you get the plain converter’s output and no error.

Azure Document Intelligence for cloud layout extraction:

  export MARKITDOWN_DOCINTEL_ENDPOINT="<endpoint>"
markitdown scan.pdf -o scan.md -d
  

Azure Content Understanding is the only path that handles video, and the only one that extracts structured fields — invoice amounts, contract dates — as YAML front matter on the output:

  export MARKITDOWN_CU_ENDPOINT="<endpoint>"
markitdown report.pdf --use-cu
  

Both Azure routes bill per convert() call. In the Python API, cu_file_types restricts which formats route to the cloud so a batch of CSVs doesn’t quietly become an invoice:

  from markitdown import MarkItDown
from markitdown.converters import ContentUnderstandingFileType

md = MarkItDown(cu_endpoint="<endpoint>", cu_file_types=[ContentUnderstandingFileType.PDF])
  

Read this before you put it behind a web form

MarkItDown does I/O with the privileges of the calling process. Like open() or requests.get(), it reaches whatever that process can reach — and convert() is deliberately permissive, accepting local paths, remote URIs, and byte streams alike.

Hand it a user-supplied string and you have handed a user your filesystem and your internal network, metadata endpoints included. The project’s own guidance is to call the narrowest function that does the job:

UseCall
Local files onlyconvert_local()
You fetch the URL yourselfconvert_response()
Maximum controlconvert_stream()

In anything hosted, validate paths, restrict URI schemes, and block private, loopback, link-local and metadata addresses before the call — not inside it.

Scope, deliberately narrow

The repository accepts converter fidelity work, bug fixes, the CLI, and the markitdown-mcp package — and explicitly refuses web servers, REST APIs, hosted services, browser frontends, and desktop apps. Those are expected to live as separate projects depending on the PyPI package. If you were hoping to upstream a GUI, don’t; build it alongside.

Next

Reading a PDF is one job; operating on one is another → Stirling PDF

Last updated 11 Sep 2026, 00:00 UTC. history