mrkeyoor.com_
Sun 20 Sept 07:01 UTC
PyPIDataupdated 20 Sept 2026

pypdf review

pypdf 6.16.2 is a pure Python toolkit for reading and rewriting existing PDF files. PdfReader exposes pages, text, metadata, forms, outlines, images, and attachments. PdfWriter can select and merge pages, change page boxes and transformations, add annotations, fill forms, encrypt, and save a new file. It neither rasterizes pages nor recognizes text inside scans. Version 6.16.2 corrects layout-mode spacing and text leading, repairs transformed annotation appearance matrices and outline removal, accepts repeated page references, and improves an RC4 fallback. Our installation measurements remain tied to 6.16.1.

Verdict

pypdf 6.16.1 installed in 0.2 seconds as one 2 MB package and imported in 0.45 seconds in our sandbox; the current registry release is 6.16.2. Use it to inspect and rewrite existing PDFs, then add separate rendering, OCR, or table tooling only when those jobs appear.

We installed it

Lab card: what happened when we installed pypdfScreenshot of pypdf documentation
Install✓ · 0.2s1 package on disk · 2 MB
Importimport pypdf in 0.45s · pure Python · py.typed · requires Python >=3.9
Known vulns0(pip-audit)

Answers from our run

Does pypdf install cleanly?

Yes. In a fresh container with an empty cache, pip install pypdf finished in 0.2s, leaving 1 package and 2 MB on disk. pip-audit reported no known vulnerabilities.

What does pypdf need to run?

Python >=3.9, and nothing compiled: it is pure Python. In our run import pypdf succeeded in 0.45s, and the package ships py.typed for type checkers.

pypdf or pdfplumber: which should you use?

pdfplumber: Choose it for words, geometry, lines, rectangles, and table extraction rather than document rewriting. pypdf 6.16.1 installed in 0.2 seconds as one 2 MB package and imported in 0.45 seconds in our sandbox; the current registry release is 6.16.2.

When should you not use pypdf?

The output is a thumbnail, preview, or printed bitmap; pypdf has no page renderer, so use a rendering engine such as PyMuPDF

API stability3/5The current 6.x API consistently uses PdfReader, PdfWriter, reader.pages, and writer methods for append, merge, metadata, forms, annotations, and encryption. The project has also removed old PyPDF2-era names after deprecation, and patch releases can change recovery from malformed files or extracted spacing. Representative document tests are safer than assuming a minor update changes only internals.
Docs5/5The official manual is organized by merging, transformations, text extraction, forms, annotations, encryption, attachments, metadata, streaming data, and migration. Its text-extraction section explains why PDF drawing operations do not equal a semantic document. Versioned API pages and a detailed changelog help separate current PdfWriter examples from years of stale PyPDF2 snippets in search results.
Maintenance5/5PyPI published 6.16.2 on August 23, 2026, and GitHub reported a push on August 26 with 137 open issues and pull requests. The patch fixed five document behaviors and one RC4 fallback only days after 6.16.1 added defensive parser limits. That pace shows active work on malformed documents, extraction output, annotations, outlines, and page-reference edge cases.
Ecosystem4/5PyPI Stats counted 35,492,171 downloads in the last week, and GitHub showed 10,171 stars. The long pypdf and PyPDF2 lineage means many document systems already speak its reader-writer model. Optional extras cover AES, images, fonts, and right-to-left text, while rendering, OCR, and table reconstruction stay in separate packages, which keeps pypdf focused but requires composition.

Use it if

  • Python code must merge, split, reorder, rotate, crop, stamp, or encrypt PDFs that already exist
  • A document pipeline needs to inspect or change metadata, outlines, attachments, annotations, or AcroForm fields
  • Deployment should remain pure Python without calling a separate PDF command-line program
  • Best-effort digital text is enough for triage and image-only pages can be handed to an OCR service
Skip it if

Setup reality

We installed pypdf 6.16.1 in a fresh Python 3.12 sandbox in 0.2 seconds. It was the only installed package and used 2 MB. pip-audit found zero known vulnerabilities, and import pypdf succeeded in 0.45 seconds. The pure Python distribution requires Python 3.9 or newer and includes py.typed. Our package metadata check counted 23 direct dependency entries across conditions and extras, and returned an unknown license value. PyPI now serves 6.16.2, so those lab figures do not claim to measure the patch release.

Ordinary page reading, writing, and text extraction work from the base package. AES operations need pypdf[crypto], image decoding can need pypdf[image], font work has a fonts extra, and right-to-left text support is also optional. Declare the required extra in the lock file before deployment. Otherwise a feature can be the first place you discover a missing optional module. There are no service credentials or required config files. PDF user and owner passwords are document inputs, not application credentials.

PdfReader can defer object reads. If it receives an open stream, keep that stream alive until page and attachment access is finished. extraction_mode='layout' estimates spacing from drawing coordinates; version 6.16.2 fixes space-width tolerance and text-leading scale, but it still does not perform OCR. Treat a blank extraction as a routing result. Any embedded or attachment filename originated inside the PDF, so replace it with an application-generated safe name before writing to disk.

Old PyPDF2 examples are a common source of wrong code. Current merging belongs to PdfWriter; removed names such as PdfFileReader, PdfFileWriter, and PdfMerger should stay out of new work. Pin a tested 6.x release and keep a sample set of real documents, since repair and extraction fixes can change output. Run untrusted files behind wall-time, memory, page-count, and size limits. Internal iteration caps in recent versions cover specific parser paths, not the whole process.

Patterns

Extract text and flag scans read-page-text

from pypdf import PdfReader

reader = PdfReader('report.pdf')
for number, page in enumerate(reader.pages, start=1):
    text = page.extract_text() or ''
    if not text.strip():
        print(f'page {number}: send to OCR')
    else:
        print(text)

A page-sized scan may look full while exposing zero text operations to pypdf.

Use layout extraction mode preserve-text-layout

from pypdf import PdfReader

page = PdfReader('columns.pdf').pages[0]
text = page.extract_text(
    extraction_mode='layout',
    layout_mode_space_vertically=False,
)
print(text)

Version 6.16.2 fixes two layout spacing behaviors, but layout mode still does not infer table cells.

Append complete and partial documents combine-pdf-files

from pypdf import PdfWriter

writer = PdfWriter()
writer.append('cover.pdf')
writer.append('chapters.pdf', pages=(0, 10))
writer.append('appendix.pdf')
writer.write('combined.pdf')
writer.close()

The page tuple follows Python slice behavior, so (0, 10) selects indexes 0 through 9.

Write one PDF per page split-into-pages

from pathlib import Path
from pypdf import PdfReader, PdfWriter

out = Path('pages')
out.mkdir(exist_ok=True)
for number, page in enumerate(PdfReader('packet.pdf').pages, start=1):
    writer = PdfWriter()
    writer.add_page(page)
    writer.write(out / f'page-{number}.pdf')

Shared resources can be copied into several outputs, so individual file sizes will not divide evenly from the source.

Rotate an existing page rotate-page

from pypdf import PdfReader, PdfWriter

page = PdfReader('sideways.pdf').pages[0]
page.rotate(90)
writer = PdfWriter()
writer.add_page(page)
writer.write('upright.pdf')

rotate() changes the page object and accepts right-angle values such as 90, 180, or 270 degrees.

Change the visible media box crop-page-box

from pypdf import PdfReader, PdfWriter

page = PdfReader('scan.pdf').pages[0]
page.mediabox.lower_left = (36, 36)
page.mediabox.upper_right = (576, 756)
writer = PdfWriter()
writer.add_page(page)
writer.write('cropped.pdf')

PDF box coordinates use points from the lower-left origin; this example removes a 36-point border from one page size.

Clone and update metadata replace-document-metadata

from pypdf import PdfWriter

writer = PdfWriter(clone_from='report.pdf')
writer.add_metadata({
    '/Title': 'Quarterly report',
    '/Author': 'Finance',
})
writer.write('report-tagged.pdf')

Document-info keys include the leading slash, and cloning retains more structure than rebuilding from selected pages.

Protect an output with AES-256 encrypt-with-aes

from pypdf import PdfWriter

writer = PdfWriter(clone_from='private.pdf')
writer.encrypt(
    user_password=reader_password,
    owner_password=owner_password,
    algorithm='AES-256',
)
writer.write('private-encrypted.pdf')

Install pypdf[crypto] and name AES-256 explicitly; encryption does not stop an authorized viewer from copying visible content.

Verify a PDF password open-encrypted-pdf

from pypdf import PdfReader

reader = PdfReader('protected.pdf')
if reader.is_encrypted and reader.decrypt(password) == 0:
    raise ValueError('incorrect PDF password')
first = reader.pages[0]

Do not access encrypted page content until decrypt() reports success.

Write images under generated names save-embedded-images

from pathlib import Path
from pypdf import PdfReader

out = Path('images')
out.mkdir(exist_ok=True)
for number, image in enumerate(PdfReader('catalog.pdf').pages[0].images, start=1):
    suffix = Path(image.name).suffix or '.bin'
    (out / f'image-{number}{suffix}').write_bytes(image.data)

Document-provided image names are untrusted. Install pypdf[image] when decoding needs Pillow.

Populate an AcroForm page fill-form-fields

from pypdf import PdfReader, PdfWriter

reader = PdfReader('form.pdf')
writer = PdfWriter(clone_from=reader)
writer.update_page_form_field_values(
    writer.pages[0],
    {'full_name': 'Ada Lovelace', 'consent': '/Yes'},
    auto_regenerate=False,
)
writer.write('form-filled.pdf')

Checkbox values use the field's export name, often /Yes; inspect reader.get_fields() instead of passing a boolean by guess.

Merge a watermark over every page overlay-watermark

from pypdf import PdfReader, PdfWriter

source = PdfReader('report.pdf')
mark = PdfReader('watermark.pdf').pages[0]
writer = PdfWriter()
for page in source.pages:
    page.merge_page(mark, over=True)
    writer.add_page(page)
writer.write('report-watermarked.pdf')

merge_page() mutates each source page; test transparency, rotations, and mismatched page boxes before batch use.

Alternatives

PackageRegistryPick it when
pdfplumberPyPIChoose it for words, geometry, lines, rectangles, and table extraction rather than document rewriting
PyMuPDFPyPIChoose it when a native engine and page rasterization are required, after reviewing its AGPL or commercial licensing options
pikepdfPyPIChoose it for lower-level object repair and transformation backed by the qpdf engine
reportlabPyPIChoose it to lay out and generate a new PDF with text, graphics, tables, and page templates

More data guides

numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.