PyMuPDF PDF Workbench
One dependency (pip install pymupdf), fully local, covering the whole PDF lifecycle: extraction, manipulation, creation, annotation, forms, and security. Two CLIs plus recipe references — load only the reference you need (progressive disclosure; don't read them all up front).
Setup
Prerequisites: python3 + pymupdf 1.23 or later (native table extraction needs 1.23). No cloud, no token, no other binaries. Both scripts exit 1 with an install hint if PyMuPDF is missing (--help works without it).
- Install PyMuPDF:
pip install "pymupdf>=1.23"
If pip refuses with "externally-managed-environment" (macOS/Linux system Python), either use a venv:
python3 -m venv ~/.venvs/pymupdf
~/.venvs/pymupdf/bin/pip install "pymupdf>=1.23"
# then invoke the scripts with that interpreter:
~/.venvs/pymupdf/bin/python3 scripts/pymupdf_parse.py /path/to/file.pdf
or force it: pip install --break-system-packages "pymupdf>=1.23".
- Verify the dependency:
python3 -c "import pymupdf; print(pymupdf.__version__)" # expect: 1.23 or later
- Smoke-test (validates dependency + a real PDF, writes nothing):
./scripts/pymupdf_parse.py /path/to/any.pdf --dry-run
# expect: "✅ Dry run OK — valid PDF, N pages. Ready to parse." (exit 0)
Optional but recommended — pymupdf4llm unlocks the higher-quality Markdown engine and layout detection:
pip install pymupdf4llm
Troubleshooting: NixOS libstdc++ import failures and other notes live in references/pymupdf-notes.md.
The two CLIs
# Extraction: single PDF or a whole directory (batch skips already-parsed docs)
./scripts/pymupdf_parse.py file.pdf --format both --tables --images --md-engine auto
./scripts/pymupdf_parse.py --dir ./pdfs/ --outroot ./pymupdf-output --tables
# Operations: merge, split, rotate, delete, render, info, meta, toc, search, encrypt, decrypt
./scripts/pdf_ops.py info file.pdf
./scripts/pdf_ops.py merge --inputs a.pdf b.pdf -o merged.pdf
./scripts/pdf_ops.py render file.pdf --pages 1-3 --dpi 150 --outroot pngs/
./scripts/pdf_ops.py encrypt file.pdf --user-pw secret -o enc.pdf
Subcommand flags (including --dry-run and --password) go after the subcommand name. Both scripts: pre-flight validation, --dry-run, exit code 0 only on success, JSON summary at the end.
Parse options
pdf positional for one file, or --dir DIR for batch (skips documents whose output folder already exists)
--pages 1-3,5 to parse a subset — real page numbers are preserved in outputs; applies to every file in batch mode
--format md|json|both (default: md)
--md-engine auto|basic|pymupdf4llm (default: auto — uses pymupdf4llm when installed, else basic)
--images to extract embedded images
--tables native table extraction via page.find_tables() — bbox + rows as lists (falls back to line-based on PyMuPDF < 1.23); ruled tables only — borderless tables need caption-page detection + mineru (see references/tables-images-layout.md)
--outroot DIR to change output root (default: ./pymupdf-output)
--lang language hint recorded in JSON output metadata (default: en)
--dry-run to validate inputs (including which batch files would be skipped) and exit without writing anything
Markdown engine guide
| Engine |
Speed |
Quality |
Notes |
auto |
— |
— |
picks pymupdf4llm if installed, else basic (default) |
pymupdf4llm |
🐢 Slower |
High |
Headers, real Markdown tables, preserves structure; needs pip install pymupdf4llm |
basic |
⚡ Fastest |
OK |
get_text("markdown"), <!-- page N --> markers per page |
Requesting pymupdf4llm explicitly exits 1 with an install hint if the package is missing; auto silently falls back to basic. The chosen engine is recorded in the JSON summary (md_engine).
Capability routing (progressive disclosure)
Load the matching reference file only when the task reaches that family:
| Task family |
Load |
CLI shortcut |
| Text extraction modes, words/spans, search, OCR |
references/extract.md |
pymupdf_parse.py, pdf_ops.py search |
Tables (find_tables), embedded images, rendering/crops, pymupdf.layout bboxes |
references/tables-images-layout.md |
--tables, --images, pdf_ops.py render |
| Merge, split, reorder, rotate, crop, delete pages |
references/manipulate.md |
pdf_ops.py merge/split/rotate/delete |
| Create PDFs: text, fonts, images, drawing, HTML (Story) |
references/create.md |
— (recipes) |
| Annotations, form filling, redaction |
references/annotate-forms-redact.md |
— (recipes) |
| Encrypt/decrypt, metadata, TOC, embedded files, links |
references/security-metadata.md |
pdf_ops.py encrypt/decrypt/meta/toc/info |
| Install issues, NixOS libstdc++ |
references/pymupdf-notes.md |
— |
Development: after changing either script or any documented claim, run python3 evals/smoke_test.py (58 self-contained cases; exits nonzero on any failure).
Error handling
- Pre-flight checks reject: missing file, non-PDF extension, empty file, corrupt PDF, password-protected PDF (supply
--password) — one-line errors
- Missing PyMuPDF exits with a clear install hint;
--help works without the dependency
- Exit code 0 only on success (or dry-run OK); 1 on invalid input, missing dependency, or failure — safe for scripting
- JSON summary block at the end of every run (op/file/pages/outputs/elapsed)
pdf_ops.py never writes in place: output must differ from input; overwritten outputs are explicit (-o)
Output conventions (parse)
./pymupdf-output/<pdf-stem>/ by default (filename without extension)
output.md (with <!-- page N --> markers in basic engine), output.json (includes lang)
images/ subdir (page-N-img-M.png), tables.json (bbox + row lists)
When to use vs. neighbors
| Need |
Tool |
| Any local PDF task, one Python dependency |
this skill |
| Fastest PDF → structured Markdown |
pdf-to-markdown skill |
| DOCX/PPTX/XLSX/images + OCR + tables |
liteparse skill |
| Highest accuracy, formulas, batch (cloud VLM) |
mineru skill |
| JS-first pipelines: Puppeteer HTML→PDF, signing, BullMQ |
pdf-tools skill |
For routing across parsers, start with the parse-docs skill.
1---2name: pymupdf-pdf3description: Local PDF workbench on PyMuPDF — extract text/tables/images to Markdown or JSON, merge/split/rotate/delete pages, render to PNG, read/write metadata and TOC, search, encrypt/decrypt, plus recipes for annotations, forms, redaction, and PDF creation. Use for any local, no-cloud PDF task; prefer mineru for VLM-grade accuracy and pdf-tools for JS/Puppeteer pipelines.4---56# PyMuPDF PDF Workbench78One dependency (`pip install pymupdf`), fully local, covering the whole PDF lifecycle: extraction, manipulation, creation, annotation, forms, and security. Two CLIs plus recipe references — load only the reference you need (progressive disclosure; don't read them all up front).910## Setup1112Prerequisites: `python3` + `pymupdf` **1.23 or later** (native table extraction needs 1.23). No cloud, no token, no other binaries. Both scripts exit 1 with an install hint if PyMuPDF is missing (`--help` works without it).13141. Install PyMuPDF:1516```bash17pip install "pymupdf>=1.23"18```1920If pip refuses with "externally-managed-environment" (macOS/Linux system Python), either use a venv:2122```bash23python3 -m venv ~/.venvs/pymupdf24~/.venvs/pymupdf/bin/pip install "pymupdf>=1.23"25# then invoke the scripts with that interpreter:26~/.venvs/pymupdf/bin/python3 scripts/pymupdf_parse.py /path/to/file.pdf27```2829or force it: `pip install --break-system-packages "pymupdf>=1.23"`.30312. Verify the dependency:3233```bash34python3 -c "import pymupdf; print(pymupdf.__version__)" # expect: 1.23 or later35```36373. Smoke-test (validates dependency + a real PDF, writes nothing):3839```bash40./scripts/pymupdf_parse.py /path/to/any.pdf --dry-run41# expect: "✅ Dry run OK — valid PDF, N pages. Ready to parse." (exit 0)42```4344Optional but recommended — `pymupdf4llm` unlocks the higher-quality Markdown engine and layout detection:4546```bash47pip install pymupdf4llm48```4950Troubleshooting: NixOS `libstdc++` import failures and other notes live in `references/pymupdf-notes.md`.5152## The two CLIs5354```bash55# Extraction: single PDF or a whole directory (batch skips already-parsed docs)56./scripts/pymupdf_parse.py file.pdf --format both --tables --images --md-engine auto57./scripts/pymupdf_parse.py --dir ./pdfs/ --outroot ./pymupdf-output --tables5859# Operations: merge, split, rotate, delete, render, info, meta, toc, search, encrypt, decrypt60./scripts/pdf_ops.py info file.pdf61./scripts/pdf_ops.py merge --inputs a.pdf b.pdf -o merged.pdf62./scripts/pdf_ops.py render file.pdf --pages 1-3 --dpi 150 --outroot pngs/63./scripts/pdf_ops.py encrypt file.pdf --user-pw secret -o enc.pdf64```6566Subcommand flags (including `--dry-run` and `--password`) go **after** the subcommand name. Both scripts: pre-flight validation, `--dry-run`, exit code 0 only on success, JSON summary at the end.6768## Parse options6970- `pdf` positional for one file, or `--dir DIR` for batch (skips documents whose output folder already exists)71- `--pages 1-3,5` to parse a subset — real page numbers are preserved in outputs; applies to every file in batch mode72- `--format md|json|both` (default: `md`)73- `--md-engine auto|basic|pymupdf4llm` (default: `auto` — uses pymupdf4llm when installed, else basic)74- `--images` to extract embedded images75- `--tables` native table extraction via `page.find_tables()` — bbox + rows as lists (falls back to line-based on PyMuPDF < 1.23); ruled tables only — borderless tables need caption-page detection + mineru (see references/tables-images-layout.md)76- `--outroot DIR` to change output root (default: `./pymupdf-output`)77- `--lang` language hint recorded in JSON output metadata (default: `en`)78- `--dry-run` to validate inputs (including which batch files would be skipped) and exit without writing anything7980### Markdown engine guide8182| Engine | Speed | Quality | Notes |83|---|---|---|---|84| `auto` | — | — | picks `pymupdf4llm` if installed, else `basic` (default) |85| `pymupdf4llm` | 🐢 Slower | High | Headers, real Markdown tables, preserves structure; needs `pip install pymupdf4llm` |86| `basic` | ⚡ Fastest | OK | `get_text("markdown")`, `<!-- page N -->` markers per page |8788Requesting `pymupdf4llm` explicitly exits 1 with an install hint if the package is missing; `auto` silently falls back to `basic`. The chosen engine is recorded in the JSON summary (`md_engine`).8990## Capability routing (progressive disclosure)9192Load the matching reference file only when the task reaches that family:9394| Task family | Load | CLI shortcut |95|---|---|---|96| Text extraction modes, words/spans, search, OCR | `references/extract.md` | `pymupdf_parse.py`, `pdf_ops.py search` |97| Tables (`find_tables`), embedded images, rendering/crops, `pymupdf.layout` bboxes | `references/tables-images-layout.md` | `--tables`, `--images`, `pdf_ops.py render` |98| Merge, split, reorder, rotate, crop, delete pages | `references/manipulate.md` | `pdf_ops.py merge/split/rotate/delete` |99| Create PDFs: text, fonts, images, drawing, HTML (Story) | `references/create.md` | — (recipes) |100| Annotations, form filling, redaction | `references/annotate-forms-redact.md` | — (recipes) |101| Encrypt/decrypt, metadata, TOC, embedded files, links | `references/security-metadata.md` | `pdf_ops.py encrypt/decrypt/meta/toc/info` |102| Install issues, NixOS libstdc++ | `references/pymupdf-notes.md` | — |103104Development: after changing either script or any documented claim, run `python3 evals/smoke_test.py` (58 self-contained cases; exits nonzero on any failure).105106## Error handling107108- Pre-flight checks reject: missing file, non-PDF extension, empty file, corrupt PDF, password-protected PDF (supply `--password`) — one-line errors109- Missing PyMuPDF exits with a clear install hint; `--help` works without the dependency110- Exit code 0 only on success (or dry-run OK); 1 on invalid input, missing dependency, or failure — safe for scripting111- JSON summary block at the end of every run (op/file/pages/outputs/elapsed)112- `pdf_ops.py` never writes in place: output must differ from input; overwritten outputs are explicit (`-o`)113114## Output conventions (parse)115116- `./pymupdf-output/<pdf-stem>/` by default (filename without extension)117- `output.md` (with `<!-- page N -->` markers in basic engine), `output.json` (includes `lang`)118- `images/` subdir (`page-N-img-M.png`), `tables.json` (bbox + row lists)119120## When to use vs. neighbors121122| Need | Tool |123|---|---|124| Any local PDF task, one Python dependency | **this skill** |125| Fastest PDF → structured Markdown | `pdf-to-markdown` skill |126| DOCX/PPTX/XLSX/images + OCR + tables | `liteparse` skill |127| Highest accuracy, formulas, batch (cloud VLM) | `mineru` skill |128| JS-first pipelines: Puppeteer HTML→PDF, signing, BullMQ | `pdf-tools` skill |129130For routing across parsers, start with the `parse-docs` skill.