PDF to Markdown
pdf-to-markdown/
Convert PDFs and Office files (Word, PowerPoint, Excel) into clean Markdown with Microsoft's markitdown, from inside Claude Code.

Convert PDFs and Office files (Word, PowerPoint, Excel) into clean Markdown with Microsoft's markitdown, from inside Claude Code.
What it does
- Converts PDF, Word (.docx), PowerPoint (.pptx) and Excel (.xlsx) files to Markdown with Microsoft markitdown.
- Keeps headings, lists and tables, which plain text extraction loses.
- Tells you when a PDF is a scan with no text layer, instead of returning an empty file. It does not do OCR.
Needs Python. The skill installs markitdown on first use: pip install "markitdown[pdf,docx,pptx,xlsx]".
Use
Ask Claude: "convert this PDF to markdown", "extract the tables from this spreadsheet" or "turn these slides into notes".
Install
Download this skill and copy the pdf-to-markdown folder to .claude/skills/pdf-to-markdown/ in your project, or to ~/.claude/skills/pdf-to-markdown/ to use it in every project. Claude Code finds it on its own; you can also call it with /pdf-to-markdown.
Source: endorphin-ai/hasbrains-agent-kit. License: MIT.
#!/usr/bin/env python3
"""Convert PDFs and Office docs to Markdown using Microsoft markitdown.
Usage:
python convert.py file1.pdf file2.docx # write .md next to each source
python convert.py ./docs -o ./markdown # convert a whole directory
python convert.py report.pdf --stdout # print to stdout instead
Supported inputs: .pdf .docx .pptx .xlsx (text-based; no OCR for scanned PDFs).
"""
import argparse
import sys
from pathlib import Path
SUPPORTED = {".pdf", ".docx", ".pptx", ".xlsx"}
def collect(paths):
files = []
for p in paths:
path = Path(p)
if path.is_dir():
files.extend(sorted(f for f in path.rglob("*") if f.suffix.lower() in SUPPORTED))
elif path.suffix.lower() in SUPPORTED:
files.append(path)
else:
print(f"warning: skipping unsupported or missing file: {p}", file=sys.stderr)
return files
def main():
ap = argparse.ArgumentParser(description="Convert documents to Markdown with markitdown.")
ap.add_argument("inputs", nargs="+", help="Files or directories to convert")
ap.add_argument("-o", "--output-dir", help="Directory to write .md files into")
ap.add_argument("--stdout", action="store_true", help="Print Markdown to stdout instead of writing files")
args = ap.parse_args()
try:
from markitdown import MarkItDown
except ImportError:
sys.exit('markitdown not installed. Run: pip install "markitdown[pdf,docx,pptx,xlsx]"')
md = MarkItDown()
files = collect(args.inputs)
if not files:
sys.exit("No supported files found (.pdf .docx .pptx .xlsx).")
out_dir = Path(args.output_dir) if args.output_dir else None
if out_dir:
out_dir.mkdir(parents=True, exist_ok=True)
exit_code = 0
for f in files:
try:
text = md.convert(str(f)).text_content or ""
except Exception as e:
print(f"error: failed to convert {f}: {e}", file=sys.stderr)
exit_code = 1
continue
if args.stdout:
print(text)
continue
target_dir = out_dir if out_dir else f.parent
target = target_dir / (f.stem + ".md")
# Avoid clobbering when two inputs share a stem (e.g. report.pdf + report.docx)
if target.exists():
target = target_dir / f"{f.stem}-{f.suffix.lstrip('.')}.md"
target.write_text(text, encoding="utf-8")
if len(text.strip()) < 20:
print(f"WARNING: {target} is nearly empty — {f.name} may be a scanned/image PDF (no OCR).", file=sys.stderr)
exit_code = 1
print(f"wrote {target}")
sys.exit(exit_code)
if __name__ == "__main__":
main()