← All articles

Convert PDF to Markdown in Python: 4 Libraries + API Compared

Published 2026-07-09

The short answer: for clean digital PDFs, pymupdf4llm gets you to Markdown in four lines of Python. For scanned pages, complex tables, or LaTeX math, local libraries plateau fast — that's where a VLM-based parsing API earns its keep. Here's working code for every option, with honest failure modes.

Option 1: pymupdf4llm (best local default)

bashClick to Copy
pip install pymupdf4llm
pythonClick to Copy
import pymupdf4llm

md = pymupdf4llm.to_markdown("paper.pdf")
with open("paper.md", "w", encoding="utf-8") as f:
    f.write(md)

Built on PyMuPDF, designed specifically for LLM ingestion: it detects headings by font size, renders tables as pipe tables, and can emit page chunks for RAG (page_chunks=True).

Where it breaks: scanned PDFs (no OCR), math (formulas come out as garbled Unicode), and merged-cell or cross-page tables.

Option 2: MarkItDown (Microsoft)

bashClick to Copy
pip install markitdown
pythonClick to Copy
from markitdown import MarkItDown

md = MarkItDown()
result = md.convert("paper.pdf")
print(result.text_content)

Microsoft's Swiss-army converter (PDF, Office, images → Markdown). Convenient when PDFs are one of many input formats in your pipeline.

Where it breaks: its PDF path is text-extraction based — same weaknesses as above, with less layout awareness than pymupdf4llm.

Option 3: Marker (best open-source quality)

bashClick to Copy
pip install marker-pdf
marker_single paper.pdf --output_dir out/

Marker runs a pipeline of ML models (layout detection, OCR via Surya, equation handling) and produces notably better structure than pure-text extractors, including decent math support.

Where it breaks: heavy dependencies and GPU appetite — practical for batch jobs on your own hardware, annoying inside a lightweight service. Tables with merged cells still fail regularly.

Option 4: pdfplumber (when you need coordinates, not Markdown)

pythonClick to Copy
import pdfplumber

with pdfplumber.open("report.pdf") as pdf:
    for page in pdf.pages:
        print(page.extract_text())
        for table in page.extract_tables():
            print(table)

Not a Markdown converter — a precision instrument for pulling specific tables and text regions when you know the document's geometry. Great for invoices with fixed layouts; wrong tool for general conversion.

Option 5: VLM parsing API (when quality is the requirement)

Everything above reads the PDF's text layer. A Visual Language Model reads the page image — which is why it survives scans, multi-column reading order, merged cells, and math. KolmoPDF's PDF parsing API wraps that in one call:

pythonClick to Copy
import time
import requests

API = "https://www.kolmopdf.com"
headers = {"Authorization": "Bearer sk-xxx"}  # get a key at /api-keys

# 1) Create job → 202 + job id (safe to disconnect after this)
with open("paper.pdf", "rb") as f:
    created = requests.post(
        f"{API}/api/v1/jobs/parse",
        headers=headers,
        files={"file": f},
        data={"table_mode": "markdown"},
    )
created.raise_for_status()
job = created.json()
job_id = job["id"]  # e.g. job_01H...

# 2) Poll status (or use webhook_url / SSE in production)
while True:
    status = requests.get(f"{API}/api/v1/jobs/{job_id}", headers=headers).json()
    if status["status"] in ("succeeded", "failed", "cancelled"):
        break
    time.sleep(2)

if status["status"] != "succeeded":
    raise RuntimeError(status.get("error") or status)

# 3) Download Markdown/ZIP
result = requests.get(f"{API}/api/v1/jobs/{job_id}/download", headers=headers)
result.raise_for_status()
open("paper_result.bin", "wb").write(result.content)

Output is GitHub-Flavored Markdown with LaTeX math in $/$$ delimiters, pipe tables, and fenced code blocks. Full Jobs API docs: /api-docs. Pricing is per page (2 credits/page) with free signup credits — try it in the browser first with the PDF to Markdown converter.

Head-to-head: what actually fails where

Inputpymupdf4llmMarkItDownMarkerVLM API
Clean single-column text
Two-column paper⚠️ order issues⚠️
Tables (simple)⚠️
Tables (merged cells / cross-page)⚠️
LaTeX math⚠️
Scanned PDF✅ (slow)
Runs fully offline

Recommendation by use case

  • RAG over clean digital PDFs: pymupdf4llm, no contest — free, fast, local.
  • Mixed-format ingestion (Office + PDF + images): MarkItDown for uniformity.
  • Batch conversion on your own GPU: Marker.
  • Papers, textbooks, scans, anything with math or serious tables: VLM API. The cost of bad parsing shows up later as bad retrieval — if the documents matter, parse them properly once.

A common production pattern: route by document type. Text-layer extraction for born-digital simple PDFs; VLM parsing for scans and technical documents. The four-line check: if pymupdf4llm's output looks scrambled, escalate that file to the API.

FAQ

What's the fastest way to convert PDF to Markdown in Python? pymupdf4llm.to_markdown("file.pdf") — one line after install. Speed isn't the bottleneck; quality on hard documents is.

How do I convert a scanned PDF to Markdown in Python? Local: Marker (bundles OCR). Hosted: a VLM API that treats every page as an image, so scans need no special handling.

Why does my extracted Markdown have formulas like "x2+y2=r2"? The PDF text layer stores rendered glyphs, not LaTeX source. Only vision-based parsing can reconstruct $x^2 + y^2 = r^2$.

Which output is best for LLM / RAG pipelines? Markdown with real heading hierarchy — headings become chunk boundaries. That's the argument for parsers that get structure right, whichever one you pick.