Reference
pdfplumber.open()
pdfplumber.open() accepts a path, an open binary file or bytes in memory, and returns a PDF object. Its options decide which pages are loaded, how to decrypt, whether to run pdfminer.six's layout analysis and whether to repair a damaged file first.
Short answer
import pdfplumber
with pdfplumber.open("samples/report.pdf") as pdf: # or a Path, an open "rb" file, or io.BytesIO(data)
page = pdf.pages[0]
print(len(pdf.pages), "pages; page 1 is", page.width, "x", page.height, "points")2 pages; page 1 is 612 x 792 pointsUse the with block so the file is closed when you are done, and extract everything you need inside it (why).
What you can pass
import io
from pathlib import Path
import pdfplumber
path = "samples/invoice.pdf"
data = Path(path).read_bytes()
with pdfplumber.open(path) as pdf: # a path (str)
print("str path: ", len(pdf.pages), "page")
with pdfplumber.open(Path(path)) as pdf: # a pathlib.Path
print("Path object: ", len(pdf.pages), "page")
with open(path, "rb") as f, pdfplumber.open(f) as pdf: # an open binary file
print("file object: ", len(pdf.pages), "page")
with pdfplumber.open(io.BytesIO(data)) as pdf: # bytes in memory
print("BytesIO: ", len(pdf.pages), "page")str path: 1 page
Path object: 1 page
file object: 1 page
BytesIO: 1 pageAnything that is a path or a binary, seekable file-like object works. Text-mode files (open(path) without "rb") do not.
From a URL, S3 or a database
pdfplumber does not download anything itself. Fetch the bytes with the client you already use and wrap them in BytesIO:
import io
import urllib.request
import pdfplumber
url = "https://raw.githubusercontent.com/jsvine/pdfplumber/stable/tests/pdfs/nics-background-checks-2015-11.pdf"
with urllib.request.urlopen(url, timeout=30) as response:
data = response.read()
with pdfplumber.open(io.BytesIO(data)) as pdf:
print(f"{len(data):,} bytes, {len(pdf.pages)} page")
print(pdf.pages[0].extract_text().splitlines()[0])90,468 bytes, 1 page
NICS Firearm Background ChecksThe same pattern works for boto3’s get_object()["Body"].read(), a database BLOB, or an uploaded file in a web framework. The cloud recipes show S3 and streaming uploads.
Options
The full signature, printed from pdfplumber 0.11.10 itself:
import inspect
import pdfplumber
print(inspect.signature(pdfplumber.open))(path_or_fp: Union[str, pathlib._local.Path, _io.BufferedReader, _io.BytesIO], pages: Union[List[int], Tuple[int], NoneType] = None, laparams: Optional[Dict[str, Any]] = None, password: Optional[str] = None, strict_metadata: bool = False, unicode_norm: Optional[Literal['NFC', 'NFKC', 'NFD', 'NFKD']] = None, repair: bool = False, gs_path: Union[str, pathlib._local.Path, NoneType] = None, repair_setting: Literal['default', 'prepress', 'printer', 'ebook', 'screen'] = 'default', raise_unicode_errors: bool = True) -> 'PDF'| Option | Default | What it does |
|---|---|---|
pages |
None (all) |
1-based page numbers to load; others are never parsed |
password |
None |
Password for encrypted PDFs |
laparams |
None |
Dict of pdfminer.six layout parameters; enables textboxhorizontals, textlinehorizontals and friends |
unicode_norm |
None |
Normalise every character’s text: "NFC", "NFKC", "NFD" or "NFKD" |
strict_metadata |
False |
Raise on unparsable metadata instead of skipping it |
repair |
False |
Rewrite the file with Ghostscript before opening |
gs_path |
None |
Path to the Ghostscript executable, if not on PATH |
repair_setting |
"default" |
Ghostscript quality preset: default, prepress, printer, ebook, screen |
raise_unicode_errors |
True |
Set False to turn UnicodeDecodeErrors in annotations into warnings (0.11.5+) |
pages
import pdfplumber
# Load only pages 1 and 3 (1-based numbers); the rest are never parsed
with pdfplumber.open("samples/bank-statement.pdf", pages=[1, 3]) as pdf:
print(len(pdf.pages), "pages loaded:", [p.page_number for p in pdf.pages])
print(pdf.pages[1].extract_text().splitlines()[0:2])2 pages loaded: [1, 3]
['Riverbend Community Bank', 'Page 3 of 3']page_number keeps the original numbering, so page 3 is still page_number == 3 even when it is the second item in pdf.pages. Loading a subset is the cheapest way to sample a large document.
password
import pdfplumber
with pdfplumber.open("samples/encrypted.pdf", password="plumber") as pdf:
print(pdf.pages[0].extract_text().splitlines()[0])Hollow Pine Supply Co. INVOICEA wrong or missing password raises PdfminerException wrapping pdfminer.six’s PDFPasswordIncorrect (error index). Owner-only restrictions (no printing, no copying) do not stop pdfplumber.
laparams
pdfplumber normally works from raw characters and does its own grouping. Passing laparams (even {}) also runs pdfminer.six’s layout analysis and exposes its results as extra object lists:
import pdfplumber
# laparams runs pdfminer.six's layout analysis and exposes its text boxes and lines
with pdfplumber.open("samples/two-column.pdf", laparams={}) as pdf:
page = pdf.pages[0]
boxes = page.textboxhorizontals
print(len(boxes), "text boxes;", len(page.textlinehorizontals), "text lines")
for box in boxes[:3]:
print(f" x0={box['x0']:5.1f} top={box['top']:5.1f} {box['text'][:48]!r}")6 text boxes; 89 text lines
x0= 60.0 top= 29.7 'THE DATA DESK · FIELD NOTES\n'
x0=495.1 top= 29.7 'November 2026\n'
x0= 60.0 top= 66.1 'Reading water-quality reports at scale\nHow colum'pdfminer.six’s text boxes follow its own grouping rules, which keep the two columns of our sample article in separate boxes. That is another route to reading order, at the cost of slower parsing. The parameters (line_overlap, char_margin, line_margin, word_margin, boxes_flow, detect_vertical, all_texts) are documented by pdfminer.six.
repair
repair=True pipes the file through Ghostscript (gs) and opens the rewritten copy, which fixes many broken cross-reference tables and truncated files. It needs Ghostscript installed. pdfplumber.repair(path, outfile=...) does the same without opening. See malformed PDFs.
Metadata
import pdfplumber
with pdfplumber.open("samples/invoice.pdf") as pdf:
for key, value in pdf.metadata.items():
print(f"{key:13} {value}")Author pdfplumber.dev sample (fictional data, CC0)
CreationDate D:20000101000000+00'00'
Creator pdfplumber.dev lab/make_samples.py
Keywords pdfplumber, sample, test
ModDate D:20000101000000+00'00'
Producer ReportLab PDF Library - (opensource)
Subject Sample invoice with a ruled line-item table
Title Invoice INV-2026-0417
Trapped Falsepdf.metadata is the document information dictionary: whatever the producer wrote. Dates are PDF date strings (D:YYYYMMDDHHmmSS).
Closing and memory
Each page caches its parsed objects the first time you touch them. For long documents without a with block, close pages as you go and close the PDF at the end:
import pdfplumber
pdf = pdfplumber.open("samples/real/federal-register-2020-17221.pdf")
total = 0
for page in pdf.pages:
total += len(page.extract_text())
page.close() # free this page's parsed objects before the next one
pdf.close() # always close what you open without a with-block
print(f"{len(pdf.pages)} pages, {total:,} characters of text")15 pages, 58,492 characters of textHow much this matters is measured on performance.