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

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.

Tested with pdfplumber 0.11.10 · Python 3.13Updated 2026-11-04Source of truth: official README ↗

Short answer

open.py
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")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
2 pages; page 1 is 612 x 792 points

Use the with block so the file is closed when you are done, and extract everything you need inside it (why).

What you can pass

open_sources.py
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")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
str path:      1 page
Path object:   1 page
file object:   1 page
BytesIO:       1 page

Anything 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:

open_from_url.py
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])
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
90,468 bytes, 1 page
NICS Firearm Background Checks

The 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:

signature.py
import inspect

import pdfplumber

print(inspect.signature(pdfplumber.open))
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
(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

selected_pages.py
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])
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
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

password.py
import pdfplumber

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

A 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:

layout_analysis.py
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}")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
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

metadata.py
import pdfplumber

with pdfplumber.open("samples/invoice.pdf") as pdf:
    for key, value in pdf.metadata.items():
        print(f"{key:13} {value}")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
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       False

pdf.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:

close_pages.py
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")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
15 pages, 58,492 characters of text

How much this matters is measured on performance.