mrkeyoor.com_
Thu 06 Aug 02:44 UTC
PyPIDataupdated 06 Aug 2026

pypdf

pypdf is a pure-Python library for taking apart and reassembling PDF files that already exist. Two classes cover almost everything: PdfReader opens a document and gives you pages, metadata, form fields, embedded images, and best-effort text; PdfWriter collects pages from one or more readers and writes a new file. With those you split, merge, rotate, crop, encrypt, decrypt, fill AcroForm fields, edit metadata, and add outline entries. It is the continuation of PyPDF2 under the py-pdf organization, so the project name changed but the lineage is the same, which is why so much old sample code no longer runs.

Verdict

The default pick for page-level PDF surgery in Python: pure, permissively licensed, actively released, and easy to reason about. It is not a text extraction engine or a renderer, so pair it with pdfplumber or PyMuPDF the moment you care about layout or pixels.

API stability3/5The PdfReader and PdfWriter core has been steady since 3.0, but majors arrive roughly yearly and each one removes previously deprecated names, and minors land every couple of weeks (6.0.0 in August 2025 to 6.14.2 in June 2026); the deprecation policy is documented and warnings come first, but you still want a pin
Docs5/5pypdf.readthedocs.io is organized by task (merging, cropping and transforming, extracting text, encryption, metadata, forms) with runnable snippets, plus a full changelog and a written deprecation policy; the README stays short and links out to it
Maintenance5/5Pushed the day before this writing, releases every week or two, security items called out in the changelog (decompression size limits in 6.0.0 and 6.13.3), and 95 open issues (133 open issues and PRs) on a 10.1k-star project
Ecosystem4/535M weekly downloads, a companion CLI in pdfly, and near-universal presence in Python document pipelines; the surrounding tooling is thinner than PyMuPDF's because anything needing rendering or heavy extraction leaves for another library

Use it if

  • You need to split, merge, reorder, rotate, or crop pages of PDFs that a user or another system produced
  • You want a pure-Python dependency with no compiler, no system libraries, and no restrictive license, which matters in locked-down build environments and AWS Lambda layers
  • You need to read or write document metadata, add outline (bookmark) entries, or fill AcroForm fields programmatically
  • You want quick text extraction for search indexing or a document pipeline where perfect fidelity is not required
Skip it if

Setup reality

pip install pypdf takes seconds, needs no compiler, and pulls in nothing at runtime except typing_extensions on Python 3.9 and 3.10. The friction is the extras: AES encryption needs pypdf[crypto], reading embedded images needs pypdf[image] for Pillow, embedding TrueType fonts needs pypdf[fonts], and pypdf[full] takes all three. You discover which one you needed by hitting an ImportError mid-run rather than at install time. The bigger time sink is stale sample code. pypdf absorbed PyPDF2, and most Stack Overflow answers still use PdfFileReader, PdfFileWriter, getPage, and PdfMerger, none of which exist anymore, so anything written before pypdf 3 needs translating before it will even import.

Patterns

Read text from every pageextract-text

from pypdf import PdfReader

reader = PdfReader("example.pdf")
print(len(reader.pages))

for page in reader.pages:
    text = page.extract_text()
    print(text)

An empty string usually means the page is a scanned image, not that extraction failed. pypdf has no OCR, so check for it and route those pages elsewhere.

Keep visual column layout when extractingextract-text-layout

page = reader.pages[0]
text = page.extract_text(extraction_mode="layout")

# tune spacing for dense pages
text = page.extract_text(
    extraction_mode="layout",
    layout_mode_space_vertically=False,
)

Default mode follows the content stream, which interleaves multi-column pages. Layout mode reconstructs spatial position and is slower, but it is the difference between usable and scrambled output on invoices and reports.

Merge whole files and page rangesmerge-pdfs

from pypdf import PdfWriter

writer = PdfWriter()
writer.append("cover.pdf")
writer.append("body.pdf", pages=(0, 10))
writer.append("appendix.pdf")

writer.write("combined.pdf")
writer.close()

PdfMerger was removed in pypdf 5.0.0; PdfWriter.append replaces it. The pages tuple is a (start, stop) slice, so (0, 10) is the first ten pages.

Write each page to its own filesplit-pages

reader = PdfReader("report.pdf")

for i, page in enumerate(reader.pages):
    writer = PdfWriter()
    writer.add_page(page)
    with open(f"page_{i + 1}.pdf", "wb") as out:
        writer.write(out)

Each output still carries the source document's shared resources, so splitting a 200-page file rarely gives you files one two-hundredth the size.

Rotate a page and trim its boxrotate-and-crop

reader = PdfReader("scan.pdf")
writer = PdfWriter()

page = reader.pages[0]
page.rotate(90)
page.mediabox.lower_left = (36, 36)
page.mediabox.upper_right = (576, 756)

writer.add_page(page)
writer.write("trimmed.pdf")

rotate() only accepts multiples of 90 and mutates the page in place. Box coordinates are PDF points (72 per inch) measured from the bottom-left corner, not pixels from the top.

Inspect document metadataread-metadata

reader = PdfReader("example.pdf")
meta = reader.metadata

print(meta.title)
print(meta.author)
print(meta.creator)
print(meta.creation_date)

reader.metadata is None for documents with no info dictionary, so guard the attribute access. Every field is optional and producers fill them inconsistently.

Set metadata on the output filewrite-metadata

writer = PdfWriter(clone_from="example.pdf")
writer.add_metadata({
    "/Title": "Q3 Report",
    "/Author": "Finance",
    "/Producer": "pypdf",
})
writer.write("tagged.pdf")

Keys must include the leading slash. clone_from copies the whole source document, which preserves outlines and form structure that a page-by-page copy would drop.

Password-protect the outputencrypt-pdf

writer = PdfWriter(clone_from="private.pdf")
writer.encrypt(
    user_password="open-me",
    owner_password="full-access",
    algorithm="AES-256",
)
writer.write("protected.pdf")

AES needs the crypto extra (pip install pypdf[crypto]) or you get an ImportError at the encrypt() call. Leaving algorithm unset falls back to weak RC4, so always name it.

Open an encrypted documentdecrypt-pdf

reader = PdfReader("protected.pdf")

if reader.is_encrypted:
    result = reader.decrypt("open-me")
    if result == 0:
        raise ValueError("wrong password")

text = reader.pages[0].extract_text()

decrypt returns a PasswordType enum whose zero value means failure; it does not raise. Touching reader.pages before decrypting raises a FileNotDecryptedError.

Pull embedded images off a pageextract-images

reader = PdfReader("catalog.pdf")
page = reader.pages[0]

for image in page.images:
    with open(image.name, "wb") as fh:
        fh.write(image.data)
    # image.image is a Pillow object when you need to resize or convert

Requires the image extra (pip install pypdf[image]) for Pillow. This returns images embedded as objects, not a rendering of the page, so vector artwork and text never appear here.

Fill AcroForm fieldsfill-form-fields

reader = PdfReader("form.pdf")
print(reader.get_fields().keys())

writer = PdfWriter(clone_from=reader)
writer.update_page_form_field_values(
    writer.pages[0],
    {"full_name": "Ada Lovelace", "agree": "/Yes"},
    auto_regenerate=False,
)
writer.write("filled.pdf")

Checkboxes take the export value with a leading slash, not True. auto_regenerate=False stops pypdf asking the viewer to rebuild appearances, which is what you want when the values should show up in every reader.

Add outline (bookmark) entriesadd-outline

writer = PdfWriter(clone_from="book.pdf")

parent = writer.add_outline_item("Part One", 0)
writer.add_outline_item("Chapter 1", 1, parent=parent)
writer.add_outline_item("Chapter 2", 12, parent=parent)

writer.write("book-with-toc.pdf")

Page numbers are zero-based indexes into writer.pages, not the printed page labels. Building pages with add_page instead of clone_from drops the source document's existing outline entirely.

Alternatives

PackageRegistryPick it when
pdfplumberPyPIYou need word and character coordinates, table extraction, or accurate reading order
pymupdfPyPISpeed matters or you need to render pages to images; check that the AGPL or a commercial license fits
pikepdfPyPILossless structural surgery on the PDF object model, backed by qpdf
reportlabPyPIGenerating new PDFs with real layout, tables, and charts rather than editing existing ones