pdfredact
Redact text in a PDF (true redaction, not just a visual overlay) using PyMuPDF. Finds occurrences of the specified text/pattern, applies a redaction annotation, and "burns" it into the page content, physically removing the underlying text (not recoverable via copy-paste or text extraction).
Installation
Requires Python 3.10 or later. Dependencies are PyMuPDF and PyYAML (for --config), both of
which publish prebuilt wheels for Linux, Windows, and macOS (no compiler required).
The package is published on PyPI as pdfredactcli
(the installed command is pdfredact).
With pip
pip install pdfredactcli
or, from a local clone of the repository:
pip install .
or, for development (editable install with test dependencies):
pip install -e .[test]
With pipx (recommended for a command-line tool)
pipx installs the tool in an isolated virtual environment and
exposes only the pdfredact command on PATH, without touching the system Python.
Linux/macOS:
python3 -m pip install --user pipx
python3 -m pipx ensurepath
pipx install pdfredactcli
Or, from a local clone of the repository, replace the last line with pipx install . (run
from the root of the repository).
Windows:
On Windows it's convenient to install pipx via Scoop, which also
manages updating Python itself if needed:
# If Scoop isn't already installed:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
scoop install pipx
pipx ensurepath
Then, in a new terminal (so ensurepath takes effect):
pipx install pdfredactcli
Or, from a local clone of the repository, run pipx install . from the repository folder
instead.
In both cases, after installation the pdfredact command is available directly in a new
terminal.
Usage
pdfredact input.pdf output.pdf -t "Mario Rossi" -t "CF: ABCDEF"
pdfredact input.pdf output.pdf -r "\bMCNP-\d{4}\b"
pdfredact input.pdf output.pdf -t "Confidential" --case-sensitive
pdfredact input.pdf output.pdf -t "Mario" --no-whole-word # also matches "Mariotti"
pdfredact input.pdf output.pdf -t "foo" --pages 1,2,5-7
pdfredact input.pdf output.pdf --box "1:56,700,300,730"
pdfredact input.pdf output.pdf -t "foo" --fill-color "#ff0000"
pdfredact input.pdf -t "Mario Rossi" # writes input_redacted.pdf
pdfredact --config job.yaml
pdfredact input.pdf output.pdf --config rules.yaml -t "extra one-off term"
pdfredact --version
The output path is optional: if omitted, it defaults to <input>_redacted.pdf next to the
input file.
Equivalent without installing, from the root of the repository:
python -m pdfredact input.pdf output.pdf -t "Mario Rossi"
Whole-word matching
By default, -t/--text terms only match on word boundaries: -t "Mario" redacts a standalone
"Mario" but leaves "Mariotti" or "mario2" untouched. This only constrains the edges of a term
that are themselves letters/digits, so a term ending in punctuation (e.g. "Confidential:")
still correctly matches even though nothing changes on that side - it just won't match inside
"NonConfidential:" either. Use --no-whole-word (or whole_word: false in --config) to go
back to plain substring matching. This only affects -t; -r/--regex patterns are never
touched, since you already have full manual control there via your own \b.
Word boundaries are decided from where the characters actually sit on the page, not just from the extracted text: many PDFs separate words (table columns, form fields, right-aligned values) by moving the text cursor instead of writing a space, so a visible gap counts as a boundary even when there is no whitespace character between the two words.
Rectangle coordinates (--box)
Format: PAGE:x0,y0,x1,y1
PAGEis 1-based (page 1 = first page)x0,y0,x1,y1in PDF points (72 pt = 1 inch), origin at the top-left (same coordinate system returned bypage.search_for())- Corner order doesn't matter: the rectangle is normalized.
- A box that falls entirely outside the page redacts nothing: it's reported on stderr and not counted as a redacted occurrence, so a typo'd coordinate can't look like a success.
Config file (--config)
Any option can also be set in a YAML file instead of retyped on every invocation:
# job.yaml
input: input.pdf # optional if given positionally on the command line
output: output.pdf # optional; defaults to <input>_redacted.pdf if unset everywhere
text: # literal terms to redact (like repeated -t)
- "Mario Rossi"
- "CF: ABCDEF"
regex: # regex patterns to redact (like repeated -r)
- '\bMCNP-\d{4}\b'
boxes: # explicit rectangles, same "PAGE:x0,y0,x1,y1" format as --box
- "1:56,700,300,730"
case_sensitive: false # like --case-sensitive
whole_word: true # like --whole-word
pages: "1,2,5-7" # like --pages
fill_color: "#000000" # like --fill-color; keep the quotes, an unquoted
# '#' would start a YAML comment
All keys are optional, and pdfredact --config job.yaml alone is a valid invocation if input
is set in the file. Values from the config file and the command line are merged:
text,regex, andboxesfrom the command line are added to the config file's lists.input,output,pages,fill_color,case-sensitive, andwhole-wordfrom the command line override the config file's value when explicitly passed. To override a config file'scase_sensitive: trueback tofalse, pass--no-case-sensitive(plain--case-sensitivecan only set it totrue); likewise--no-whole-wordoverrides a config file'swhole_word: trueback tofalse.
Each key is validated the same way as its CLI equivalent (same --box/--pages/--fill-color
formats); an unknown key or a value of the wrong type/shape fails immediately with exit code 2
rather than being silently ignored.
Exit codes
0 = success, 2 = input/usage error.
Known limitations
- Only PDF input is accepted. PyMuPDF can also open TXT, EPUB, SVG, CBZ and image files, but redaction annotations are PDF-only, so those inputs are rejected with exit code 2; convert them to PDF first.
- Document metadata (Author, Title, XMP) and annotation/comment content are not handled,
since they don't appear in
get_text(). - A term split across multiple lines in the PDF layout might not be found.
- Scanned PDFs (image-only, with no extractable text) require OCR upstream: the tool finds nothing to redact in that case.
- Whole-word matching (the default for
-t) only checks the character immediately before/after a match on the same line, so it's most reliable for terms that don't themselves span a line break. If a PDF packs two words so tightly that the extracted text runs them together with no gap at all, there is no boundary left to find:--no-whole-wordis the fallback there.
Always verify the output with pdftotext and pdfinfo -meta before distribution.
Windows compatibility
The project is tested in CI on Linux, Windows, and macOS (see .github/workflows/tests.yml)
and is compatible with Windows without modifications: it only uses os.path (no hardcoded
separators), no POSIX-only calls, and os.path.samefile has worked correctly on Windows
since Python 3.2.
Development
pip install -e .[test]
pytest
pytest tests/test_core.py::test_redact_pdf_literal_term # single test
AI-assisted development
This project's code, tests, and documentation were developed with the assistance of AI tools (Claude Code). Every change was reviewed before being published; please report any issues you find via the project's issue tracker.
License
MPL-2.0. The repository is REUSE compliant; to verify:
pipx run reuse lint.
Metadata
Release files for pdfredactcli 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pdfredactcli-0.2.2.tar.gz | 45.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pdfredactcli-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.4 kB
Release files / pdfredactcli-0.2.2.tar.gz
| Download URL | pdfredactcli-0.2.2.tar.gz |
|---|---|
| Size | 45.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6940e20d95f2b114c054aa17f1ec99802a0f2255ddb083e3990daf582a126fee
|
|
BLAKE2b-256 checksum How to use checksums |
11bc360ba69c00237caa9498c74c6d818596e6656017d7337a807ae3bfab876a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 16, 2026.
Transparency logRelease files / pdfredactcli-0.2.2-py3-none-any.whl
| Download URL | pdfredactcli-0.2.2-py3-none-any.whl |
|---|---|
| Size | 23.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
77af90a7c988b325ce96f59e3debf6a0813108d1caef9205761a92b439b15020
|
|
BLAKE2b-256 checksum How to use checksums |
aa901128dcbeee8f948b76bff8cc0e05a25922a95f41199f3a9186dbb9fb2dcd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 16, 2026.
Transparency log