A supplier emails over a signed contract. Three pages of it belong in the archive, the intake form stapled to the back needs its fields flattened before anything downstream reads them, and the whole bundle has to come back out as one document. That is pypdf's work, and a requirements file pinned to PyPDF2 3.0.1 will never receive another fix for any of it, because that release of 31 December 2022 is the last the project shipped and every patch since has landed under the pypdf name. What produces the new document at the end of that bundle is a separate question with a separate answer.
Full disclosure. The team behind IronPDF is ours at Iron Software. This read weighs what pypdf's absent renderer and monthly patch cadence ask of a team, and what IronPDF writes when the document does not exist yet.
What Do the Maintainers Say Is Out of Scope?
Start with the boundary, because the maintainers state it themselves rather than leaving it to be inferred. Asked in a GitHub discussion whether the library could act as a viewer, maintainer pubpub-zz answered that pypdf is a library to work on PDF elements and not a virtual viewer, pointing the asker toward pdftocairo or the pdf2image wrapper. Turning a page into pixels and turning markup into a document both sit outside the library by design, which is a scope decision rather than a missing feature.
Text extraction is bounded by the format as well, and the documentation says so. An image-only page, usually a scan, yields text that is minimal or effectively empty, tables usually reduce to positioned text with no reliable column and row structure, and pulling text from a complex layout means parsing the page's entire content stream, which costs memory on large files. Each of those is the point where a second tool joins the diagram, and signing or stamping the finished document usually joins it at the same seam.
Which Jobs Does the Manipulation Surface Still Own?
Within the category of PDFs that already exist, the scope is wide. The documentation lists merging, splitting, cropping and rotating pages, reading and writing metadata and outlines, encrypting and decrypting, editing viewer preferences, working with attachments and annotations, stamping watermarks, and reading, filling, and flattening AcroForm fields, all in one package rather than three stitched together. The install stays light with it, since the core needs nothing beyond typing_extensions below Python 3.11, with extras for AES encryption, image handling, and font work. Development runs under the py-pdf organization on GitHub at a fast cadence, with 41 releases in the past twelve months and at least two in every one of those months, and 6.16.2 current as of 23 August 2026. For a workflow that takes apart and reassembles documents it did not create, that surface is the ceiling of what a manipulation library needs to reach, and IronPDF answers the producing half of the same pipeline from one package.
Filling a form is one pass through a reader and a writer.
from pypdf import PdfReader, PdfWriter
reader = PdfReader("intake-form.pdf")
writer = PdfWriter()
writer.append(reader)
# auto_regenerate=False stops the viewer recomputing field appearances,
# which is what triggers a save-changes prompt in some readers
writer.update_page_form_field_values(
writer.pages[0],
{"applicant_name": "Jordan Rivera", "date": "2026-08-25"},
auto_regenerate=False,
)
with open("intake-form-filled.pdf", "wb") as f:
writer.write(f)
That writes a filled copy of the intake form with the two values in place and the field appearances already baked in. The equivalent step on a document a renderer has just produced is filling the form on the same object that rendered it, with no second package in the path.
Which Package Name Does the Fix Land In?
The rename is not a fork and not an abandonment. The PyPDF2 listing on PyPI states the project went back to its roots, names 3.0.1 as its final release, and points all further development at pypdf from 3.1.0 onward. Three advisories are PyPDF2-only and closed on that side, namely CVE-2022-24859 for an inline-image infinite loop fixed in 1.27.5, CVE-2023-36810 for quadratic-runtime parsing of a malformed xref fixed in 1.27.9, and CVE-2023-36807 for an infinite loop on malformed objects fixed in 2.10.6.
CVE-2023-36464 is the one that spans the rename, and it is the clearest reason a stale pin is a standing risk. PyPDF2 2.2.0 through 3.0.1 is listed as affected with no PyPDF2 fix available, while the equivalent pypdf range, 3.1.0 through 3.9.0, was patched in pypdf 3.9.0. An old PyPDF2 pin therefore has no upgrade path inside its own name, and the remediation is a package change rather than a version bump, which is a migration ticket rather than a dependency bump on whatever sprint discovers it. Sitting on one documented API surface that does not change names is worth something at exactly that moment.
A Fix Lands Here Most Months
The GitHub Advisory Database's PyPI filter returns dozens of published advisories against pypdf's own code, and the volume reads correctly only with the class attached. Nearly all of them are availability findings, meaning infinite loops or memory exhaustion triggered by malformed input, rather than remote code execution or data disclosure. The highest score on the record is CVE-2026-59935 at CVSS 8.7, an infinite loop on improperly terminated inline images, patched in 6.14.2, with CVE-2026-59936 the same class of finding against the same code path, patched in 6.14.1. The same shape recurs through the record with CVE-2026-54531 for an infinite loop parsing outlines in the writer, fixed in 6.13.0, CVE-2026-54651 for an infinite loop processing threads and articles, fixed in 6.13.1, CVE-2026-33699 for an infinite loop during stream recovery, fixed in 6.9.2, and manipulated-stream memory-exhaustion findings including CVE-2026-48735, CVE-2026-41314, and CVE-2026-41312.
Every one of those carries a fixed version, so the record describes maintainers who patch quickly. The architectural consequence is a standing upgrade budget rather than a one-time integration, because a service parsing documents from outside sources has to track a package that publishes fixes most months, and each bump is a regression test against every document shape the pipeline handles. That obligation belongs in the same review as the encryption and metadata settings on the output side, and it is the recurring cost a comparison of feature lists never shows.
| Capability | pypdf 6.16.2
|
IronPDF for Python |
|---|---|---|
| Merge, split, rotate, and crop pages | Supported through PdfWriter
|
The same object that rendered the document |
| Metadata and outlines | Read and write | Read and write without a second package |
| Encryption | Encrypt and decrypt | Encrypt, decrypt, set permissions |
| AcroForm fields | Read, fill, and flatten | Read, fill, and flatten on the rendered file |
| Text out of an existing PDF | Supported, with documented structural limits |
ExtractAllText across every page |
| HTML or CSS into a new PDF | No rendering engine in the library | Chromium through RenderHtmlAsPdf
|
| A PDF page as an image | Outside scope, maintainers point at pdf2image
|
Rasterising built into the same API |
| Licence | BSD-3-Clause License | Commercial, one tier |
| Python versions accepted | 3.9 to 3.14 | 3.7 and later |
Table 1. Operations on existing and new documents, pypdf against IronPDF for Python, as each project documents itself.
The sixth and seventh rows are where a pipeline gains a second dependency. Everything above them is work both packages already do.
Where IronPDF Takes the Outbound Half
Producing a new document starts from markup or a URL rather than from a file that already exists.
from ironpdf import *
renderer = ChromePdfRenderer()
# A page the reporting app already serves, rendered as the browser draws it
pdf = renderer.RenderUrlAsPdf("https://internal.example.com/reports/monthly-summary")
pdf.SaveAs("monthly-summary.pdf")
That writes the rendered report to disk with its stylesheet applied and its script already run. The same object takes a template file through RenderHtmlFileAsPdf and a string through RenderHtmlAsPdf, and once the document exists, merging it with other files is part of the same API rather than a second install.
IronPDF writes the document that does not exist yet and then merges, stamps, encrypts, and signs it on the same object, so the outbound half is one package rather than a renderer bolted onto a manipulation library on a monthly patch cycle. The narrow case that stays with pypdf is page surgery on documents somebody else produced. IronPDF has a free trial if you want to run your existing template through the generation half while pypdf keeps doing the inbound work.
Which side does most of your work land on, taking apart documents that already exist or producing ones that do not exist yet? Tell us in the comments, especially if you are running pypdf and a renderer together and have opinions about where the seam belongs.
pypdf and PyPDF2 are the property of their maintainers, and we have no affiliation with the py-pdf organization. The release, licence, and advisory details above are based on the projects' own repositories, PyPI listings, and the published CVE records at the time of writing. If a detail has moved since, the comments are open.
Top comments (0)