pdfblah
A precise, non-destructive Swiss army knife for PDFs, from your terminal or a local app in your browser. Replace, redact, and remove text, scrub or anonymize personal data, edit metadata, watermark, stamp, number, split, merge, rotate, crop, encrypt, render, compare, and fill forms. Fonts, spacing, and alignment stay perfect, and nothing you didn't ask for is touched.
Most tools "edit" a PDF by painting a box over the old text and drawing new text
on top, which leaves the original underneath (copy and paste still reveals it) and
often adds a watermark. pdfblah rewrites the real text in the content stream, so:
- the old text is genuinely gone (
pdftotext, Ctrl-F, and copy show only the new value) - no overlay, no watermark
- your metadata (dates, Producer, XMP) is kept byte for byte, unless you choose to edit it
- alignment is auto-detected and kept, so right-aligned numbers stay flush
- fonts it cannot reproduce are refused instead of garbled
Pure Python. No system dependencies.
Install
One line installs the CLI and the local app:
# macOS / Linux
curl -fsSL https://pdfblah.com/install.sh | sh
# Windows (PowerShell)
irm https://pdfblah.com/install.ps1 | iex
Or straight from PyPI:
pipx install pdfblah # recommended, isolated; or: pip install pdfblah
uv tool install pdfblah # or with uv
Pure Python (3.9+), no system dependencies. Nothing is compiled or signed.
Run it in your browser
Prefer the web interface, running on your own machine? One command opens the same tool as pdfblah.com in your browser, fully local, with no upload, no watermark, and no account:
pdfblah gui
Drop in a PDF, add your rules, and save the edited file to your computer. Nothing leaves your machine. Or keep going for the command line.
Use
Replace the first match:
pdfblah in.pdf out.pdf --find "Old Name" --replace "New Name"
Options:
--scope all change every match (default: first)
--scope 3 change the 3rd match
--ci ignore case
--word whole word only ("cat" will not match "category")
--regex treat --find as a regex (\1 backrefs work in --replace)
--page 2 only page 2
--replace "" delete the text
Many rules from a file (FIND | REPLACE | FLAGS per line):
pdfblah in.pdf out.pdf --rules rules.txt
# rules.txt
Old Company Name | New Company Name | all
CONFIDENTIAL DRAFT | FINAL | ci
Jane Doe | John Smith | all word
Total | Sum | 2
delete this phrase |
Commands
The same engine (locate real text, then substitute something) as four presets.
redact removes the matched text for real (gone from pdftotext, Ctrl-F and copy)
and draws a bar over each spot. --no-bar removes the text with no mark.
pdfblah redact in.pdf out.pdf --find "Account 12345"
pdfblah redact in.pdf out.pdf --find "\d{3}-\d{2}-\d{4}" --regex # every SSN
scrub finds structured personal data (email, IBAN, credit card, SSN, phone) and removes it, or masks it. Cards and IBANs are checksum-validated, so ordinary numbers are left alone.
pdfblah scrub in.pdf out.pdf
pdfblah scrub in.pdf out.pdf --types email,credit_card --mask "[redacted]"
anonymize replaces detected data with realistic, shape-preserving fakes so a
document is safe to share. The same value maps to the same fake; --seed makes it
reproducible. Names are swapped only when you list them.
pdfblah anonymize in.pdf out.pdf --names "Alison Cohen,Matthew Reider" --seed 7
merge fills a template once per data row: every {{column}} placeholder becomes
that row's value, one output PDF per row.
pdfblah merge template.pdf people.csv --out ./letters --name-col name
meta reports everything metadata-ish (DocInfo, XMP, pages, version, encryption), which often reveals more than you expect (author, software, timestamps). With an output file it can strip or set fields. Every other command keeps metadata intact.
pdfblah meta in.pdf # report what's in there
pdfblah meta in.pdf clean.pdf --strip # remove all metadata
pdfblah meta in.pdf out.pdf --set author="Jane Roe" # set a field
Metadata edits can also ride along with any other command, or live in a rules file:
pdfblah redact in.pdf out.pdf --find "Acme Corp" --strip-metadata
pdfblah in.pdf out.pdf --find OLD --replace NEW --set-metadata author="Ops"
# rules.txt
@strip-metadata
@set-metadata author = Redacted Dept
CONFIDENTIAL | PUBLIC | all
Toolkit
Beyond editing text, pdfblah does the page-level and document-level jobs you usually reach for several tools to do. All keep your metadata intact.
Pages
pdfblah combine a.pdf b.pdf c.pdf -o all.pdf # concatenate
pdfblah split in.pdf -o parts/ --every 1 # or --ranges 1-3 4-6
pdfblah pages in.pdf out.pdf --keep 3,1,2 # keep / reorder (or --drop 4)
pdfblah rotate in.pdf out.pdf --degrees 90 --pages 1-2
pdfblah crop in.pdf out.pdf --margins 20,20,20,20
Marks (honest overlays, drawn on top; your text underneath is untouched)
pdfblah watermark in.pdf out.pdf --text DRAFT --tile --opacity 0.2 --rotation 45
pdfblah stamp in.pdf out.pdf --image logo.png --position top-right
pdfblah number in.pdf out.pdf --format "Page {n} of {total}"
pdfblah bates in.pdf out.pdf --prefix ACME --digits 6 --start 1
Security & size
pdfblah protect in.pdf out.pdf --password secret --no-copy # AES-256 + permissions
pdfblah unlock in.pdf out.pdf --password secret
pdfblah optimize in.pdf out.pdf --downsample-dpi 150 # shrink the file
pdfblah attachments in.pdf -o out.pdf --add report.csv # list/add/extract files
Render, extract, inspect
pdfblah render in.pdf -o images/ --dpi 150 --format png
pdfblah extract in.pdf --text # or --images -o out/
pdfblah form in.pdf --list # or --fill data.json out.pdf [--flatten]
pdfblah compare a.pdf b.pdf --visual --out-dir diff/
pdfblah signatures in.pdf --validate # read/validate (needs pdfblah[sign])
Library
from pdfblah import process, redact, scrub, anonymize, merge, apply_rules
process("in.pdf", "out.pdf", "999.00", "42.00", scope="all", ci=True)
process("in.pdf", "out.pdf", r"\d{4}-\d{4}", "REDACTED", scope="all", regex=True)
redact("in.pdf", "out.pdf", "Account 12345")
scrub("in.pdf", "out.pdf", types=["email", "credit_card"])
anonymize("in.pdf", "out.pdf", names=["Alison Cohen"], seed=7)
merge("template.pdf", [{"name": "Alice"}], "./out")
Each call returns a report dict (ok, count, refused, reason, ...).
What it does not do
Scanned PDFs (image only, no text layer) cannot be edited. Fonts that are not embedded and not standard, or use a custom encoding, are refused rather than rendered wrong. This is by design: a wrong-looking edit is worse than a clear "no".
Guides
Step by step, with pictures, at pdfblah.com/guides:
- Find and replace text in a PDF
- Redact a PDF (really remove the text)
- Remove personal data (PII) from a PDF
- View, strip, or edit PDF metadata
- Bulk find and replace in a PDF
- Run pdfblah on your own machine
Two ways to use it
- On your machine (free, this package): the command line, or the local app in
your browser with
pdfblah gui. Nothing is uploaded. - Online at pdfblah.com: the same edit in the browser with nothing to install, handy for a quick one-off or a non-technical colleague. Upload, preview free, download.
License
MIT, (c) 2026 Kuvop LLC.
Release files for pdfblah 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pdfblah-0.8.0.tar.gz | 84.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pdfblah-0.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 171.2 kB
Release files / pdfblah-0.8.0.tar.gz
| Download URL | pdfblah-0.8.0.tar.gz |
|---|---|
| Size | 84.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c3014829e432a0eb455c428aba5b779da9c022cf652bf8473019efd61a1a9d60
|
|
BLAKE2b-256 checksum How to use checksums |
b514bffba81e14c9881b099c9214c5311c7145a6e41acf695846c51f1b3b0e2a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 7, 2026.
Transparency logRelease files / pdfblah-0.8.0-py3-none-any.whl
| Download URL | pdfblah-0.8.0-py3-none-any.whl |
|---|---|
| Size | 86.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b96dc613efb59975bd38b9db6130639641c3217768ae977d5cb47f099713fc96
|
|
BLAKE2b-256 checksum How to use checksums |
9a006e51fe6749753c4a5d80914f9446f982b511043ecc807830803e898f5776
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 7, 2026.
Transparency log