Independent guide. Not affiliated with the pdfplumber project or its maintainers.Official repository: jsvine/pdfplumber ↗

Compare

pdfplumber vs pypdf

They solve different problems. pypdf reads and writes PDF files: merge, split, rotate, encrypt, fill forms. pdfplumber reads what is on the page: characters with positions, lines, tables. Most document pipelines that need both use both, and they work well together.

Tested with pdfplumber 0.11.10 · Python 3.13Updated 2026-11-04

Short answer

Task pdfplumber pypdf
Extract plain text Yes Yes, about 9× faster in our test
Text with coordinates, fonts, colours Yes Limited (via a visitor function)
Tables Yes No
Crop, filter, search regions Yes No
Merge, split, rotate, reorder pages No Yes
Encrypt, decrypt, fill forms, add metadata Reads encrypted files Yes
Licence, size MIT, 58.7 MB BSD-3-Clause, 3.9 MB

Reading content → pdfplumber. Changing files → pypdf. Use both when you do both.

PyPDF2, pypdf and pyPdf

Three names, one lineage. pyPdf was the original library. PyPDF2 was a fork that became the popular one and, after years of parallel development, its maintainers merged everything back into pypdf in December 2022. PyPDF2’s last release is 3.0.1 (31 December 2022); pypdf has continued since, at version 6.21.4 when we tested. If a tutorial says import PyPDF2, the modern equivalent is import pypdf, with mostly the same class names (PdfReader, PdfWriter).

Text

text_both.py
import pdfplumber
import pypdf

path = "samples/report.pdf"

with pdfplumber.open(path) as pdf:
    print("pdfplumber:", pdf.pages[0].extract_text().splitlines()[2:5])

reader = pypdf.PdfReader(path)
print("pypdf:     ", reader.pages[0].extract_text().splitlines()[2:5])
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
pdfplumber: ['1. Summary', 'Forty-eight sites were inspected between March and September 2026. Most sites passed; sites marked Follow-up', 'need a second visit before 31 December 2026.']
pypdf:      ['Annual inspection report', '1. Summary', 'Forty-eight sites were inspected between March and September 2026. Most sites passed; sites marked Follow-up']

The text is the same; the line breaks differ slightly because each library has its own rules for grouping characters into lines. pypdf was 9× faster on our 15-page test document (0.17 s against 1.60 s) and lighter on memory, because it does not build an object for every character.

Tables: pypdf has none

pypdf_table.py
import pypdf

page = pypdf.PdfReader("samples/invoice.pdf").pages[0]
lines = page.extract_text().splitlines()
start = lines.index("HP-1042")
print(lines[start:start + 6])     # one table row, flattened into separate lines
print("table methods:", [name for name in dir(page) if "table" in name.lower()])
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
['HP-1042', 'Stainless hex bolts M8 x 40 (box of 100)', '12', '18.40', '220.80', 'HP-2210']
table methods: []

Each cell becomes its own line, and the next row follows straight on. You could rebuild columns from that only if you already know the column count and no cell is ever empty or wrapped. pdfplumber’s table finder uses the ruling lines and positions instead, and returns rows and columns directly.

Using them together

pypdf writes, pdfplumber reads. Merging two documents with pypdf and extracting the tables from the result with pdfplumber:

merge_then_extract.py
import pdfplumber
from pypdf import PdfWriter

# pypdf writes: merge the invoice and the first page of the report
writer = PdfWriter()
writer.append("samples/invoice.pdf")
writer.append("samples/report.pdf", pages=[0])
writer.write("merged.pdf")

# pdfplumber reads: pull the tables back out of the new file
with pdfplumber.open("merged.pdf") as pdf:
    for page in pdf.pages:
        table = page.extract_table()
        print(f"page {page.page_number}: {len(table)} rows, header {table[0]}")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
page 1: 8 rows, header ['Item', 'Description', 'Qty', 'Unit price', 'Amount']
page 2: 15 rows, header ['Site', 'Result', 'Reading (ppb)']

Filling a form with pypdf and reading the stored values back with pdfplumber’s access to the document catalog (details on form fields):

fill_then_read.py
import pdfplumber
from pypdf import PdfReader, PdfWriter

writer = PdfWriter(clone_from=PdfReader("samples/form.pdf"))
writer.update_page_form_field_values(writer.pages[0], {"department": "Research", "needed_by": "2027-01-15"},
                                     auto_regenerate=False)
writer.write("form-filled.pdf")

# Read the stored values back with pdfplumber (see the form fields guide)
with pdfplumber.open("form-filled.pdf") as pdf:
    fields = pdf.doc.catalog["AcroForm"].resolve()["Fields"]
    for ref in fields:
        field = ref.resolve()
        name = field["T"].decode() if isinstance(field["T"], bytes) else field["T"]
        if name in ("department", "needed_by"):
            value = field.get("V")
            print(name, "=", value.decode() if isinstance(value, bytes) else value)
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
department = Research
needed_by = 2027-01-15

Both open encrypted files when you give them the password:

encrypted_both.py
import pdfplumber
import pypdf

reader = pypdf.PdfReader("samples/encrypted.pdf")
print("pypdf encrypted:", reader.is_encrypted, "| decrypt:", reader.decrypt("plumber").name)

with pdfplumber.open("samples/encrypted.pdf", password="plumber") as pdf:
    print("pdfplumber:", pdf.pages[0].extract_text().splitlines()[0])
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
pypdf encrypted: True | decrypt: USER_PASSWORD
pdfplumber: Hollow Pine Supply Co. INVOICE

When pypdf alone is enough

If you only need the text of each page, in reading order, to index or search it, pypdf is smaller, faster and has no binary dependencies at all. Switch to pdfplumber when you need tables, positions, region crops, fonts, or the table and object visual debugging.