Skip to content

I keep reaching for Microsoft's MarkItDown

Article4 min read
  • #tools
  • #markdown
  • #llm

I hit the same wall a lot: I've got a PDF, or a slide deck, or someone's Excel export, and I want its contents in a prompt. Copy-paste mangles the tables. Half the PDF-to-text libraries I've tried give me a wall of text with the reading order scrambled. So I was quietly pleased to find that Microsoft had shipped a small tool that does exactly this one job — MarkItDown — and does it well enough that it's now a default in my toolbox.

It's a Python package and a CLI. You point it at a file, it hands you back Markdown. That's the whole pitch, and the restraint is the point.

What it actually eats

The format list is longer than I expected the first time I read it:

  • PDF, Word, PowerPoint, Excel
  • Images — it pulls EXIF metadata and runs OCR
  • Audio — EXIF plus speech transcription
  • HTML, and the text-ish formats: CSV, JSON, XML
  • ZIP archives, which it just walks and converts piece by piece
  • YouTube URLs and EPubs

So "convert this quarterly report PDF" and "transcribe this voice memo" and "flatten this zip of mixed docs" are all the same command. That breadth is what makes it stick — I don't have to remember a different library per file type.

Using it

Install with the extras and you're done:

pip install markitdown[all]

From the shell it's a one-liner, which is how I use it most of the time:

markitdown report.pdf > report.md

And the Python API is about as small as an API gets:

from markitdown import MarkItDown
 
md = MarkItDown()
result = md.convert("deck.pptx")
print(result.text_content)

No config objects, no builder pattern, no ceremony. convert() and read .text_content. I appreciate a library that trusts me to just call it.

Why Markdown, specifically

The README makes an argument I think is right, and it's worth stating because it explains the whole design. Markdown sits extremely close to plain text — minimal markup — while still carrying the structure that matters: headings, lists, tables, links. It's also what the models were trained on. GPT-4o-class models speak Markdown natively; they'll reach for it in their answers unprompted. And it's token-efficient, so you're not paying for a pile of HTML tags or JSON scaffolding just to get a document into context.

Put those together and Markdown is close to the ideal interchange format between "a file a human made" and "something you're about to hand a language model."

The caveat that matters

Straight from the docs, and worth repeating so nobody's surprised: the output is meant to be consumed by text-analysis tools, not treated as a high-fidelity conversion for human reading. If you feed it a heavily-designed PDF, the tables can come out rough and the reading order isn't always perfect. Scanned pages lean on OCR, and OCR is OCR.

That's not a knock — it's the tool being honest about its lane. If I want a pixel-perfect document, this isn't it. If I want the content of a document in a form a model can work with, it's usually the fastest path there.

Two things I only found later

There's a plugin system — off by default — so you can slot in extra converters. The one I've seen mentioned is markitdown-ocr, which routes OCR through an LLM vision API when the built-in path isn't enough. You discover others by searching the #markitdown-plugin tag on GitHub.

And there's a companion markitdown-mcp server, which exposes the same conversion over the Model Context Protocol. That's the part I'm most curious about — it means an agent can convert a file itself, mid-task, instead of me shelling out ahead of time and pasting the result. I haven't wired that into anything real yet, but it's the natural next step for how I already use the CLI.

None of this is flashy. It's a small tool that picked one job and refused to scope-creep, and that's exactly why it's earned a permanent spot in the way I get documents in front of a model.