Skip to main content

pdfreplace

Replace a string inside a PDF, in place, without re-typesetting the document.

$ uvx pdfreplace invoice.pdf "2025" "2026"
p.1  Invoice date: 01 January «2025»
p.3  Valid through December «2025»

2 match(es). Preview only, nothing written. To apply: -o OUTPUT.pdf

$ uvx pdfreplace invoice.pdf "2025" "2026" -o fixed.pdf
2 replacement(s) -> fixed.pdf

There is no separate search command: without -o it is a dry run. Nothing is written until you name an output file.

Install

uvx pdfreplace ...      # no install at all
pipx install pdfreplace # or keep it around
pip install pdfreplace

The only dependency is pikepdf, whose wheels ship libqpdf. No system packages to install.

Why not just edit the raw stream

The obvious approach is qpdf --qdf, a sed, and fix-qdf. It works on the PDF a tutorial shows you and fails on most real ones, because producers split text across elements to apply kerning:

[(Hello W) -20 (orld)] TJ

A byte search for Hello World finds nothing there. pdfreplace parses the content stream into operators, joins each run of text back together, searches that, and distributes the replacement across the original elements. Kerning numbers inside the match are folded into a single carry value so the rest of the line does not drift.

What it will refuse to do

Silent corruption is worse than an error, so pdfreplace stops rather than guess:

  • Subset fonts. Producers embed only the characters a document actually uses. If you ask for a character that has no glyph, a viewer stacks it invisibly on top of the next one and the page quietly turns to mush. pdfreplace checks the font's widths first and refuses, naming the characters it cannot render.
  • Type0 / Identity-H fonts. There the bytes are glyph numbers, not text. Reading them back requires the ToUnicode map and writing them requires glyphs that are usually absent from the subset. Those runs are reported and left untouched.
  • Digitally signed PDFs are edited if you insist, but you get told that any change invalidates the signature.

Pages without a match keep their original content stream byte for byte.

Known limits

  • Text inside form XObjects and AcroForm field values is not searched yet.
  • A replacement of a different length shifts the rest of the line. A PDF stores final positions, so nothing re-wraps; keep lengths close if the layout is tight.
  • /Differences encodings are read as Latin-1, which is right for ASCII and can be wrong for unusual characters above it.

Exit codes

Code Meaning
0 matches found (and written, if -o was given)
1 no match
2 could not open the file, or refused to write

Development

uv run --with pikepdf --with pytest pytest

MIT licensed.

Metadata

Release files for pdfreplace 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pdfreplace 0.1.0
File Size Uploaded
pdfreplace-0.1.0.tar.gz 8.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pdfreplace 0.1.0
File Interpreter ABI Platform
pdfreplace-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 16.8 kB

Release files / pdfreplace-0.1.0.tar.gz

Download URL pdfreplace-0.1.0.tar.gz
Size 8.9 kB
Tags Source
SHA-256 checksum
How to use checksums
0c010fff8205921ee013ba7e89a89e9a3b07321ced8abade5d81eee747b602d0
BLAKE2b-256 checksum
How to use checksums
4a82f22b9da50469a2bc9864bb354e832f4ebc488fffa366e61bfe0c8fd77154
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 Sep 14, 2026.

Transparency log

Release files / pdfreplace-0.1.0-py3-none-any.whl

Download URL pdfreplace-0.1.0-py3-none-any.whl
Size 7.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0fe15b1980872d7df547965064f205ace8b841250b656a52c6c6b6dacd57f5b6
BLAKE2b-256 checksum
How to use checksums
0e0755a94e7f4db70f75f759a27f5dab9e3500dc5a5e290ce743ef2bf4ad43b0
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 Sep 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page