Overview
MarkItDown is Microsoft's Python package and CLI for converting documents, media, and web content to Markdown for LLM and text-analysis pipelines. It preserves structure that matters to models (headings, lists, tables, links) and is not meant for high-fidelity, human-facing document conversion. This skill targets markitdown 0.1.x (current release 0.1.8, Sept 2026; Python 3.10–3.14).
When to Use This Skill
Use this skill when the user wants to:
- Convert a PDF, Word, PowerPoint, Excel, EPUB, HTML, or Outlook file into Markdown for an LLM prompt, RAG index, or text analysis
- Batch-convert a folder of mixed document formats
- Get a YouTube transcript or audio transcription as Markdown
- Add LLM-written image descriptions, or OCR text from scans and embedded images
- Expose file conversion to an agent through the
markitdown-mcpserver
Does NOT Trigger
| Scenario | Use Instead |
|---|---|
| Ingesting many sources into a self-hosted notebook with chat, search, or podcasts | alterlab-open-notebook |
| Answering questions across the sections, figures, and tables of one PDF | alterlab-pdf-explore |
| Building a one-row-per-paper evidence table from many PDFs | alterlab-pdf-extract |
| Managing references, DOIs, or BibTeX libraries | alterlab-pyzotero |
Supported Formats
| Format | Description | Notes |
|---|---|---|
| Portable Document Format | Text-layer extraction ([pdf]); scans need OCR (see below) |
|
| DOCX | Microsoft Word | Headings, lists, tables ([docx]) |
| PPTX | PowerPoint | Slide text, tables, notes; optional LLM image descriptions ([pptx]) |
| XLSX / XLS | Excel spreadsheets | One Markdown table per sheet ([xlsx], [xls]) |
| Images | JPEG, PNG | EXIF metadata (needs exiftool) and an LLM description when llm_client is set |
| Audio | WAV, MP3, M4A, MP4 | Metadata + speech transcription ([audio-transcription]) |
| HTML | Web pages | Clean conversion; Wikipedia and Bing result pages get dedicated handling |
| CSV / JSON / XML | Text-based data | CSV becomes a table; JSON, JSONL, and XML pass through as text; RSS/Atom feeds are parsed |
| ZIP | Archive files | Iterates contents |
| EPUB | E-books | Full text extraction |
| Outlook MSG | Email messages | Headers and body ([outlook]) |
| Jupyter | .ipynb notebooks |
Markdown and code cells |
| YouTube | Video URLs | Fetches transcripts ([youtube-transcription]) |
Quick Start
Installation
# Install with all features (uv-first; uv pip works inside an active venv)
uv pip install 'markitdown[all]'
# Or run the CLI ad hoc without installing into the project env
uvx --from 'markitdown[all]' markitdown document.pdf -o output.md
# Or from source
git clone https://github.com/microsoft/markitdown.git
cd markitdown
uv pip install -e 'packages/markitdown[all]'
Command-Line Usage
# Basic conversion
markitdown document.pdf > output.md
# Specify output file
markitdown document.pdf -o output.md
# Pipe content (give a type hint when reading from stdin)
cat document.pdf | markitdown -x pdf > output.md
# Keep embedded images as data URIs instead of truncating them
markitdown slides.pptx --keep-data-uris -o slides.md
# Plugins
markitdown --list-plugins # list installed plugins
markitdown --use-plugins document.pdf -o output.md
Python API
from markitdown import MarkItDown, StreamInfo
md = MarkItDown()
result = md.convert("document.pdf")
print(result.markdown) # result.text_content is a soft-deprecated alias
print(result.title) # may be None
# Local files only (narrower than convert(), which also fetches URIs)
result = md.convert_local("document.pdf")
# From a binary stream; stream_info replaces the deprecated file_extension= keyword
with open("document.pdf", "rb") as f:
result = md.convert_stream(f, stream_info=StreamInfo(extension=".pdf"))
Advanced Features
1. AI-Enhanced Image Descriptions
Use an LLM via OpenRouter to describe images (built-in support covers image files and pictures inside PPTX):
import os
from markitdown import MarkItDown
from openai import OpenAI
# OpenRouter takes the dotted slug of an Anthropic model ID (claude-opus-5-5 -> anthropic/claude-opus-5.5);
# scripts/convert_with_ai.py derives it from $ALTERLAB_MODEL (skills/core/shared/model_env.md).
model = "anthropic/claude-opus-5.5"
# Initialize OpenRouter client (OpenAI-compatible API)
client = OpenAI(
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="https://openrouter.ai/api/v1"
)
md = MarkItDown(
llm_client=client,
llm_model=model, # recommended for scientific vision
llm_prompt="Describe this image in detail for scientific documentation"
)
result = md.convert("presentation.pptx")
print(result.markdown)
2. OCR for Scans and Embedded Images
MarkItDown has no local OCR engine; the converters read existing text layers. Two supported routes add OCR:
-
markitdown-ocrplugin (Microsoft, 0.1.x): LLM-vision OCR for images inside PDF, DOCX, PPTX, and XLSX, with full-page OCR for scanned PDFs. It reuses the samellm_client/llm_modelas image descriptions; without a client it silently falls back to the normal converters.uv pip install markitdown-ocr openaimd_ocr = MarkItDown(enable_plugins=True, llm_client=client, llm_model=model) # client/model as above result = md_ocr.convert("scanned_paper.pdf") -
Azure Document Intelligence (cloud OCR and layout analysis) — see the next section.
For fully local OCR, add a text layer first (e.g. ocrmypdf scan.pdf searchable.pdf, which uses Tesseract) and then convert the result.
3. Azure Document Intelligence and Content Understanding
# Document Intelligence (endpoint on the command line or in MARKITDOWN_DOCINTEL_ENDPOINT)
markitdown document.pdf -o output.md -d -e "<document_intelligence_endpoint>"
# Content Understanding (documents, images, audio, video; needs the [az-content-understanding] extra)
markitdown recording.mp4 --use-cu --cu-endpoint "<content_understanding_endpoint>"
from markitdown import MarkItDown
md = MarkItDown(docintel_endpoint="<document_intelligence_endpoint>")
result = md.convert("complex_document.pdf")
print(result.markdown)
Authentication uses the AZURE_API_KEY environment variable if set, otherwise DefaultAzureCredential (Azure CLI login, managed identity, etc.); pass docintel_credential= to supply one explicitly. Both services send the document to Azure, so check that this is allowed for the data you are converting.
4. Plugin System
Plugins are disabled by default. Enable them with --use-plugins on the CLI or MarkItDown(enable_plugins=True) in Python. Find third-party plugins with the GitHub hashtag #markitdown-plugin; see references/api_reference.md for writing one.
5. MCP Server
markitdown-mcp (0.0.1 alpha series) exposes one tool, convert_to_markdown(uri), for http:, https:, file:, and data: URIs.
uv pip install markitdown-mcp
markitdown-mcp # STDIO transport (default)
markitdown-mcp --http --host 127.0.0.1 --port 3001 # Streamable HTTP + SSE on localhost
# Register with Claude Code
claude mcp add markitdown -- uvx markitdown-mcp
Set MARKITDOWN_ENABLE_PLUGINS=true in the server environment to use installed plugins. The server has no authentication and can read any file its user can, so keep it on localhost (upstream recommends the Docker image for Claude Desktop).
Optional Dependencies
Control which file formats you support:
# Install specific formats (no spaces inside the bracket — pip parses 'pdf, docx' as bad names)
uv pip install 'markitdown[pdf,docx,pptx]'
# All available options:
# [all] - All optional dependencies
# [pptx] - PowerPoint files
# [docx] - Word documents
# [xlsx] - Excel spreadsheets
# [xls] - Older Excel files
# [pdf] - PDF documents
# [outlook] - Outlook messages
# [az-doc-intel] - Azure Document Intelligence
# [az-content-understanding] - Azure Content Understanding
# [audio-transcription] - WAV and MP3 transcription
# [youtube-transcription] - YouTube video transcription
Common Use Cases
1. Convert a Folder of Papers
from pathlib import Path
from markitdown import MarkItDown
md = MarkItDown()
pdf_dir, output_dir = Path("papers/"), Path("markdown_output/")
output_dir.mkdir(exist_ok=True)
for pdf_file in pdf_dir.glob("*.pdf"):
try:
result = md.convert_local(pdf_file)
except Exception as e: # keep going; report failures at the end
print(f"✗ {pdf_file.name}: {e}")
continue
(output_dir / f"{pdf_file.stem}.md").write_text(result.markdown)
For larger jobs with logging and parallelism, use scripts/batch_convert.py.
2. Extract Data from Excel for Analysis
from markitdown import MarkItDown
result = MarkItDown().convert("data.xlsx")
print(result.markdown) # one Markdown table per sheet
3. Convert PowerPoint with AI Descriptions
import os
from markitdown import MarkItDown
from openai import OpenAI
# OpenRouter takes the dotted slug of an Anthropic model ID (claude-opus-5-5 -> anthropic/claude-opus-5.5);
# scripts/convert_with_ai.py derives it from $ALTERLAB_MODEL (skills/core/shared/model_env.md).
model = "anthropic/claude-opus-5.5"
# Use OpenRouter for access to multiple AI models
client = OpenAI(
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="https://openrouter.ai/api/v1"
)
md = MarkItDown(
llm_client=client,
llm_model=model, # recommended for presentations
llm_prompt="Describe this slide image in detail, focusing on key visual elements and data"
)
result = md.convert("presentation.pptx")
with open("presentation.md", "w") as f:
f.write(result.markdown)
4. Extract YouTube Video Transcription
from markitdown import MarkItDown
md = MarkItDown()
result = md.convert("https://www.youtube.com/watch?v=VIDEO_ID")
print(result.markdown)
Docker Usage
# Build image (from the repository root)
docker build -t markitdown:latest .
# Run conversion
docker run --rm -i markitdown:latest < ~/document.pdf > output.md
Best Practices
1. Choose the Right Conversion Method
- Simple documents: Use basic
MarkItDown() - Complex layouts or scanned PDFs: Azure Document Intelligence, or the
markitdown-ocrplugin - Visual content: Enable AI image descriptions
- Untrusted inputs: Use
convert_local()orconvert_stream()rather thanconvert()
2. Handle Errors Gracefully
from markitdown import MarkItDown, FileConversionException, UnsupportedFormatException
md = MarkItDown()
try:
result = md.convert_local("document.pdf")
print(result.markdown)
except FileNotFoundError:
print("File not found")
except UnsupportedFormatException:
print("No converter for this format (is the matching extra installed?)")
except FileConversionException as e:
print(f"Conversion error: {e}")
3. Optimize for Token Efficiency
Markdown output is already token-efficient, but you can collapse blank lines and strip metadata you do not need:
import re
clean_text = re.sub(r"\n{3,}", "\n\n", result.markdown).strip()
Integration with Scientific Workflows
Convert Literature for Review
from markitdown import MarkItDown
from pathlib import Path
md = MarkItDown()
# Convert all papers in literature folder
papers_dir = Path("literature/pdfs")
output_dir = Path("literature/markdown")
output_dir.mkdir(exist_ok=True)
for paper in papers_dir.glob("*.pdf"):
result = md.convert(str(paper))
# Save with metadata
output_file = output_dir / f"{paper.stem}.md"
content = f"# {paper.stem}\n\n"
content += f"**Source**: {paper.name}\n\n"
content += "---\n\n"
content += result.markdown
output_file.write_text(content)
# For AI-enhanced conversion with figures
import os
from openai import OpenAI
# Model ID via the ALTERLAB_MODEL convention (skills/core/shared/model_env.md).
model = "anthropic/claude-opus-5.5" # OpenRouter slug of the ALTERLAB_MODEL default (skills/core/shared/model_env.md)
client = OpenAI(
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="https://openrouter.ai/api/v1"
)
md_ai = MarkItDown(
llm_client=client,
llm_model=model,
llm_prompt="Describe scientific figures with technical precision"
)
Security
MarkItDown reads files and fetches URLs with the privileges of the current process. When any part of the input comes from an untrusted user (hosted tools, agents acting on web content), validate paths and URLs first, block private and metadata-service addresses, and call the narrowest method (convert_local(), convert_stream(), or convert_response() on a response you fetched yourself) instead of convert().
Troubleshooting
Common Issues
-
Missing dependencies (
MissingDependencyException): install the feature extrauv pip install 'markitdown[pdf]' # For PDF support -
Binary file errors:
convert_stream()requires a binary streamwith open("file.pdf", "rb") as f: # Note the "rb" result = md.convert_stream(f, stream_info=StreamInfo(extension=".pdf")) -
Empty output from a scanned PDF or image: MarkItDown found no text layer. Use the
markitdown-ocrplugin, Azure Document Intelligence, or add a text layer locally withocrmypdfbefore converting. Installing Tesseract alone does not enable OCR in MarkItDown. -
No EXIF metadata for images or audio: install
exiftool(or pointEXIFTOOL_PATHat it).
Performance Considerations
- PDF files: Large PDFs take time; there is no page-range option, so split very large files first if needed
- Reuse one
MarkItDown()instance across files; creating it loads the file-type detector - Audio transcription: Sends the audio to Google's Web Speech API via the
speech_recognitionpackage (network required, slow for long files); don't use it on confidential research recordings such as participant interviews unless your ethics approval allows third-party processing - AI image descriptions / OCR plugin: One API call per image (costs and rate limits apply)
Next Steps
- See
references/api_reference.mdfor complete API documentation, custom converters, and plugins - Check
references/file_formats.mdfor format-specific details - Review
scripts/batch_convert.pyfor automation examples - Explore
scripts/convert_with_ai.pyfor AI-enhanced conversions
Resources
- MarkItDown GitHub: https://github.com/microsoft/markitdown
- PyPI: https://pypi.org/project/markitdown/ (also
markitdown-ocr,markitdown-mcp) - OpenRouter: https://openrouter.ai (for AI-enhanced conversions)
- OpenRouter API Keys: https://openrouter.ai/keys
- OpenRouter Models: https://openrouter.ai/models
- Plugin Development: See
packages/markitdown-sample-pluginin the MarkItDown repository
Part of the AlterLab Academic Skills suite.