MarkItDown: Convert Files to Markdown for LLMs in Python (2026)
Large language models read Markdown far better than raw Word, Excel or PDF bytes. Headings, bullet lists and tables survive as plain text the model understands, and Markdown is compact, so you spend fewer tokens. MarkItDown is an open-source Python library from Microsoft that converts Office documents, PDFs, HTML, CSV, JSON, images and more into Markdown with one call. This tutorial was tested with markitdown 0.1.9, python-docx 1.2.0, openpyxl 3.1.5 and pandas 3.1.0 on Python 3.13.5, and every output block below is the real output of the scripts shown.
Why MarkItDown in 2026?
- One API for many formats:
.docx,.xlsx,.pptx,.pdf,.html,.csv,.json,.epub, ZIP files and more. - Keeps document structure: headings become
#, lists become*and tables become Markdown tables. - Built for LLM and RAG pipelines, where clean text in means better answers out.
- Lightweight: install only the converters you need with extras such as
[docx,xlsx,pdf]. - Also ships a command line tool and an MCP server for AI assistants.
If you need deep PDF layout analysis with OCR and table detection, compare it with Docling for PDF to Markdown. MarkItDown is the faster, simpler choice when your files are mostly digital Office documents and text PDFs.
Install
pip install "markitdown[docx,xlsx,pdf]"
# or everything: pip install "markitdown[all]"
Step 1: Create sample files
To keep the tutorial self-contained, we create a Word report with python-docx, an Excel sheet with openpyxl and a small PDF with reportlab (pip install python-docx openpyxl reportlab).
from docx import Document
from openpyxl import Workbook
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
doc = Document()
doc.add_heading("Q3 Sales Report", level=1)
doc.add_paragraph("Revenue grew 18% compared with Q2.")
doc.add_heading("Top regions", level=2)
for r in ("North: 42,000 USD", "South: 35,500 USD", "East: 28,250 USD"):
doc.add_paragraph(r, style="List Bullet")
t = doc.add_table(rows=1, cols=2)
t.rows[0].cells[0].text, t.rows[0].cells[1].text = "Product", "Units"
for p, u in (("Laptop", "120"), ("Monitor", "340")):
row = t.add_row().cells; row[0].text, row[1].text = p, u
doc.save("report.docx")
wb = Workbook(); ws = wb.active; ws.title = "Orders"
ws.append(["order_id", "customer", "amount"])
for row in ((1001, "Ali", 250.0), (1002, "Sara", 99.5), (1003, "John", 410.25)):
ws.append(row)
wb.save("orders.xlsx")
c = canvas.Canvas("policy.pdf", pagesize=A4)
c.drawString(72, 800, "Remote Work Policy 2026")
c.drawString(72, 780, "Employees may work remotely up to three days per week.")
c.drawString(72, 760, "Requests must be approved by a team lead.")
c.save()
print("created report.docx, orders.xlsx, policy.pdf")
created report.docx, orders.xlsx, policy.pdf
Step 2: Convert Word, Excel and PDF to Markdown
Create a MarkItDown object once and call convert() with a file path, URL or stream. The result has a text_content attribute holding the Markdown.
from importlib.metadata import version
from markitdown import MarkItDown
md = MarkItDown(enable_plugins=False)
print("markitdown", version("markitdown"))
for name in ("report.docx", "orders.xlsx", "policy.pdf"):
result = md.convert(name)
print(f"===== {name} ({len(result.text_content)} chars) =====")
print(result.text_content.strip())
Real output
markitdown 0.1.9
===== report.docx (207 chars) =====
# Q3 Sales Report
Revenue grew 18% compared with Q2.
## Top regions
* North: 42,000 USD
* South: 35,500 USD
* East: 28,250 USD
| | |
| --- | --- |
| Product | Units |
| Laptop | 120 |
| Monitor | 340 |
===== orders.xlsx (136 chars) =====
## Orders
| order\_id | customer | amount |
| --- | --- | --- |
| 1001 | Ali | 250.00 |
| 1002 | Sara | 99.50 |
| 1003 | John | 410.25 |
===== policy.pdf (124 chars) =====
Remote Work Policy 2026
Employees may work remotely up to three days per week.
Requests must be approved by a team lead.
The Word heading levels became # and ##, the bullet list became Markdown bullets and the Word table became a pipe table. Note that python-docx tables have no header row, so MarkItDown adds an empty header line; real documents with a styled header row look cleaner. Each Excel sheet becomes a ## section with a table, and the PDF text is extracted paragraph by paragraph.
Step 3: Batch convert a folder and chunk it for RAG
In a real project you convert a whole folder, save the .md files and split the text into chunks with source metadata before sending them to an embedding model.
from pathlib import Path
from markitdown import MarkItDown
md = MarkItDown()
out = Path("markdown"); out.mkdir(exist_ok=True)
chunks = []
for src in sorted(Path(".").glob("*.*")):
if src.suffix not in {".docx", ".xlsx", ".pdf"}:
continue
text = md.convert(src).text_content
(out / f"{src.stem}.md").write_text(text, encoding="utf-8")
for i, para in enumerate(p for p in text.split("\n\n") if p.strip()):
chunks.append({"source": src.name, "chunk": i, "text": para.strip()})
print("converted:", sorted(p.name for p in out.iterdir()))
print("total chunks:", len(chunks))
for c in chunks[:3]:
print(c)
hits = [c for c in chunks if "remote" in c["text"].lower()]
print("chunks mentioning 'remote':", [(c["source"], c["chunk"]) for c in hits])
Real output (batch)
converted: ['orders.md', 'policy.md', 'report.md']
total chunks: 9
{'source': 'orders.xlsx', 'chunk': 0, 'text': '## Orders\n| order\\_id | customer | amount |\n| --- | --- | --- |\n| 1001 | Ali | 250.00 |\n| 1002 | Sara | 99.50 |\n| 1003 | John | 410.25 |'}
{'source': 'policy.pdf', 'chunk': 0, 'text': 'Remote Work Policy 2026'}
{'source': 'policy.pdf', 'chunk': 1, 'text': 'Employees may work remotely up to three days per week.'}
chunks mentioning 'remote': [('policy.pdf', 0), ('policy.pdf', 1)]
Three files produced nine chunks, each tagged with its source file and position. The simple keyword search found the two policy chunks about remote work. In production you would replace the keyword match with embeddings and a vector store, as shown in our guide to production RAG pipelines in Python.
Command line usage
markitdown report.docx -o report.md
markitdown policy.pdf > policy.md
cat orders.xlsx | markitdown -x xlsx
Production tips
- Install only the extras you need to keep containers small.
- Scanned PDFs have no text layer; use OCR (for example Docling or an LLM image description plugin) for those.
- Split on Markdown headings rather than fixed character counts so chunks keep their context.
- Store the source file name and chunk number with every chunk so answers can cite their source.
- Treat files from users as untrusted: run conversion with size limits and timeouts.
- Pin the markitdown version, because the project is still pre 1.0 and output details can change.
Conclusion
MarkItDown turns Word, Excel, PDF and many other files into clean Markdown with a single convert() call. With markitdown 0.1.9 on Python 3.13 you can build an LLM-ready document loader, batch convert folders and produce chunks with source metadata for your RAG system in a few lines of code.