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.
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
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])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
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()])['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:
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]}")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):
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)department = Research
needed_by = 2027-01-15Both open encrypted files when you give them the password:
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])pypdf encrypted: True | decrypt: USER_PASSWORD
pdfplumber: Hollow Pine Supply Co. INVOICEWhen 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.