Tutorial5 hours ago

1. Installing MarkItDown, and the Python 3.9 Trap That Installs 2.6 KB of Nothing

On a stock Mac, pip reported success in 6.18 seconds and installed a 2.6 kB placeholder with no code in it. With uv, the real thing took 26.97 seconds and converted a Word file in 0.46.

The WJS Desk

Sep 25, 2026 ยท 10 min read

Photo by Kindel Media on Pexels

By the end of this part you will have MarkItDown 0.1.8 installed in its own environment, a handful of real documents converted to Markdown, and a clear picture of the one install mistake almost every Mac user makes first. That mistake does not fail. It prints "Successfully installed", leaves you with a 2.6 kB package containing no code, and no markitdown command.

Everything below was run on an Apple M4 Pro with 48 GB of memory, macOS 26.5.1 (build 25F80) and Homebrew 7.0.6, timed with /usr/bin/time -p. Download times depend on your connection, so treat those as ours rather than yours.

StepTimeNotes
pip install on the Mac's own Python 3.9.66.18 s"Succeeds", installs a 2.6 kB placeholder, no command
brew install uv15.32 suv 0.12.19
uv venv with a Python 3.13 it had to download2.54 s3.13.15, fetched by uv
uv pip install 'markitdown[all]'26.97 sCold cache, 49 packages, 318 MB on disk
uv pip install 'markitdown[pdf,docx]'14.96 s30 packages, 200 MB
Converting a one line text file0.43 sAlmost all of it is Python start-up

What MarkItDown is, and why it gets four parts

MarkItDown is a Python library and command line tool from Microsoft that turns files into Markdown: PDF, Word, PowerPoint, Excel, EPUB, HTML, CSV, JSON, images, audio and a few web pages with special handling. Its README is unusually honest about the goal. The output is "meant to be consumed by text analysis tools", and it "may not be the best option for high-fidelity document conversions for human consumption". In plain terms: it exists to get documents into an LLM, not to make them pretty.

That framing explains the numbers. When we checked on 25 September 2026 the repo had 186,978 stars and 13,796 forks, it was created on 13 November 2024, and it is MIT licensed. Version 0.1.8 shipped on 21 September, four days before we installed it. The GitHub API counts roughly 130 contributors. Two Microsoft maintainers (afourney and gagb) account for most of the history, but 97 commits landed between 1 July and today from more than 50 different authors, so this is not a one-person project that goes quiet when someone changes jobs. It also has 368 open pull requests and 327 open issues, which matters in part 3.

We are spending four parts on it because "convert this pile of files so an AI can read it" is the most common unglamorous job in anyone's LLM workflow, and because MarkItDown is the tool that search results, blog posts and agent frameworks keep pointing at. We wanted to know whether it deserves that. Short version for part 1: installing it is easy once you dodge one trap, and the first conversions are good. Part 2 is where it gets interesting.

Prerequisites

  • A Mac with Homebrew. We used Homebrew 7.0.6.
  • Python 3.10 to 3.14. You probably do not have that yet, even if python3 works. More on that in a second.
  • About 320 MB of disk for the full install, or 200 MB for PDF and Word only.
  • Roughly 15 minutes, most of it reading.

No account, no API key and no payment is needed for anything in this part. MarkItDown has optional features that call Azure or an OpenAI-compatible model, and we did not use them, because we are not paying for a key to write a tutorial. We flag every place that boundary matters.

The wrong turn: pip on the Python your Mac already has

Here is what a reasonable person types first, and what we typed first:

mkdir -p ~/markitdown-test && cd ~/markitdown-test
python3 --version
python3 -m venv venv39
source venv39/bin/activate
pip install 'markitdown[all]'

On our Mac, python3 --version said 3.9.6. That is the Python Apple ships with the Command Line Tools, and it is below MarkItDown's 3.10 floor. You would expect pip to refuse. It does not. It went looking for any version of markitdown that claims to support 3.9, found one, and installed it in 6.18 seconds:

Downloading markitdown-0.0.1a1-py3-none-any.whl (2.6 kB)
WARNING: markitdown 0.0.1a1 does not provide the extra 'all'
Successfully installed markitdown-0.0.1a1

Version 0.0.1a1 is a name-reservation placeholder from before the project launched. The package contains two files, __init__.py and __about__.py, and no converter code at all. Then:

markitdown --help
zsh: command not found: markitdown

Gotcha: the only hint you get is the single WARNING line about the missing all extra, buried in pip's output. If you see markitdown-0.0.1a1 anywhere, you are on the wrong Python. Delete that environment with deactivate && rm -rf venv39 and start again below.

To be fair to MarkItDown, the README says 3.10 or later in its first section, and this is standard pip behaviour rather than a bug in the project. But the result is a "successful" install of nothing, and it is the first thing a Mac user will hit.

The install that works

We used uv, a Python package manager that can also download the right Python for you. It is what the MarkItDown README suggests as its second option, and it turned out to be the fastest path by a wide margin.

brew install uv
cd ~/markitdown-test
uv venv --python=3.12 .venv
source .venv/bin/activate
uv pip install 'markitdown[all]'
markitdown --version

The last line should print markitdown 0.1.8 (or whatever is current when you read this). The Homebrew install of uv took 15.32 seconds. Creating the environment took 0.09 seconds for us, because Homebrew had already put Python 3.12.14 on this machine. To see what happens when you do not have a suitable Python, we asked uv for 3.13, which we did not have. It downloaded and unpacked Python 3.13.15 in 2.54 seconds. So the "I only have 3.9" problem costs about three seconds to fix with uv.

The [all] install pulled 49 packages in 26.97 seconds with an empty cache and left a 318 MB environment. Note the quotes around 'markitdown[all]': zsh treats square brackets as a pattern, and without quotes you get zsh: no matches found.

Where the 318 MB goes

MarkItDown itself is small. Its optional dependencies are not. The biggest folders in our environment:

PackageSizeWhy it is there
onnxruntime77 MBRuns Magika, Google's file type detector
pandas48 MBExcel conversion
speech_recognition43 MBAudio transcription
numpy24 MBUsed by pandas and onnxruntime
lxml21 MBHTML and XML parsing

Magika is installed even without extras, which is why the slim install is still 200 MB. It is also why MarkItDown can identify a file whose name lies about what it is, which we test in part 2.

The slim install, and what happens when you pick it

If you only need PDF and Word, install just those:

uv venv --python=3.12 slim
uv pip install --python slim/bin/python 'markitdown[pdf,docx]'

That took 14.96 seconds, 30 packages and 200 MB. We then fed it a PowerPoint file to see how it fails. It fails well. After a traceback, the last lines were:

PptxConverter recognized the input as a potential .pptx file, but the dependencies
needed to read .pptx files have not been installed. To resolve this error, include
the optional dependency [pptx] or [all] when installing MarkItDown.

Exit code 1, and a message that tells you the fix. We would take that error over most we have seen this year.

Your first conversions, on documents nobody wrote as a demo

The repo ships test files, but a tool that only works on its own test files is not a tool. So we pulled real public documents: a Word court directions template and a PowerPoint deck from GOV.UK, Mary Shelley's Frankenstein as an EPUB from Project Gutenberg, and the World Bank's population spreadsheet. Copy these as-is:

mkdir -p docs out
curl -sSL -o docs/lp1-directions.docx https://assets.publishing.service.gov.uk/media/65aa6178b2f3c60013e5d5fd/LP1_0124.docx
curl -sSL -o docs/agfs-slides.pptx https://assets.publishing.service.gov.uk/media/5ae1a40ee5274a0d82094937/agfs-engagement-slides.pptx
curl -sSL -o docs/frankenstein.epub https://www.gutenberg.org/ebooks/84.epub3.images
curl -sSL -o docs/wb_pop.xls 'https://api.worldbank.org/v2/en/indicator/SP.POP.TOTL?downloadformat=excel'

Then convert. There are three ways to call it, and all three worked for us:

markitdown docs/lp1-directions.docx -o out/lp1.md
markitdown docs/frankenstein.epub > out/frankenstein.md
cat docs/wb_pop.xls | markitdown > out/wb_pop.md

The third one surprised us. We piped a spreadsheet in on standard input with no file name and no -x xls hint, and MarkItDown still worked out it was Excel. That is Magika earning its 77 MB.

Here is what came back, timed warm (second run onwards):

DocumentInOutTime
GOV.UK directions (.docx)59,812 bytes6,096 bytes0.46 s
GOV.UK deck, 10 slides (.pptx)1,348,586 bytes13,958 bytes0.46 s
Frankenstein (.epub)474,161 bytes442,642 bytes, 78,229 words0.49 s
World Bank population (.xls)262,656 bytes299,383 bytes1.01 s

Since an empty text file takes 0.43 seconds, most of those times are Python starting up and loading its libraries, not conversion. That matters if you plan to call the command once per file across thousands of files. Part 3 does the batch version properly.

The Word file is the best result. Bold stayed bold, the numbered list stayed numbered, and the layout tables came out as Markdown pipe tables. The EPUB is excellent too: the title, author, language and date came out as a metadata header, then 34 headings for the book's title page, letters and chapters. The PowerPoint gave one # heading per slide title with a <!-- Slide number: 3 --> comment above it. The spreadsheet is where the first cracks show, and part 2 is about exactly that.

Pro tip: images inside documents are cut down to a stub like ![](data:image/png;base64...) by default. Add --keep-data-uris if you want them. On the Word file that took the output from 6,096 bytes to 54,111, nine times larger for one logo. For LLM input, leave the default alone.

What broke

Four things, in the order we hit them.

The Python 3.9 placeholder. Covered above. Pip "succeeded" and installed a package with no code in it. Fix: use a 3.10+ Python, which uv gives you in under three seconds.

OpenDocument files are not supported. We also grabbed a GOV.UK statistics table published as .ods, the LibreOffice spreadsheet format that UK government departments use a lot:

curl -sSL -o docs/jts0101.ods https://assets.publishing.service.gov.uk/media/618270f48fa8f5297b6440de/jts0101.ods
markitdown docs/jts0101.ods

That ended in a 16-line Python traceback and UnsupportedFormatException: Could not convert stream to Markdown. No converter attempted a conversion, exit code 1. Fair enough, ODS is not on the supported list, and there is an open issue (#1437) and pull request (#1940) for ODT text documents. But anyone in government, education or a LibreOffice shop should know this before building on it. Pandoc reads ODT, and we compare the two in part 4.

A truncated Word file. We cut the DOCX to its first 20,000 bytes to simulate a broken download. MarkItDown reported DocxConverter threw BadZipFile with message: File is not a zip file and exited 1. That is the right behaviour: loud, with the reason.

Tracebacks instead of messages. A missing file gives a full Python traceback ending in FileNotFoundError. It is correct, just noisy. If you are wrapping MarkItDown in a script, check the exit code rather than grepping the output.

The one we have not shown yet is the worst. Some inputs exit 0 and produce an empty file with no warning at all. That is the start of part 2.

Common mistakes

  • Running pip install markitdown on the system Python and getting 0.0.1a1. Check with markitdown --version, which should print 0.1.x.
  • Forgetting the quotes around 'markitdown[all]' in zsh.
  • Using plain pip install inside a uv environment. The README warns about this: use uv pip install, or pip may install into a different Python.
  • Opening a new terminal and forgetting source .venv/bin/activate, then getting command not found and assuming the install broke.
  • Installing the slim extras and then feeding it a PowerPoint or Excel file. The error tells you which extra to add.
  • Expecting LibreOffice formats (.odt, .ods, .odp) to work.

What we did not test in this part

We installed on one Mac with Apple Silicon. We did not try Intel Macs, Linux or Windows, and we did not try Python 3.10, 3.11 or 3.14, only 3.12 and 3.13. We did not install with conda. All timings are from one machine on one connection. The install times include downloads, so yours will differ with your bandwidth. The conversion times do not include downloads and should be closer to what you see on similar hardware.

Getting rid of it

Everything lives in one folder, so the escape hatch is one line:

deactivate; rm -rf ~/markitdown-test

If you installed uv only for this, brew uninstall uv removes it. Any Python that uv downloaded lives under ~/.local/share/uv/python and can be deleted with uv python uninstall 3.13.

Next

Part 2 feeds MarkItDown the documents people actually need converted: a tax form, a research paper, a scanned page, a recording and a web page. It handles some of them better than we expected. With others it exits 0 and gives you text an LLM will happily summarise, even though half the words are missing.

Share

pip said "Successfully installed markitdown" in 6.18 seconds. It was a 2.6 kB placeholder with no code in it. Here is the install that actually works on a Mac. #MarkItDown #Python #OpenSource #LLM

Never miss a ship

The best stuff that shipped this week, delivered every Thursday. Free, no spam. We read all the boring stuff so you get the fun parts.

Keep reading