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

Reference

Pages

A PDF in pdfplumber is mostly a list of Page objects. Each page knows its number, size and rotation, holds its own objects, and has every extraction method. Counting pages is instant; the content is only parsed when you ask for it.

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

Short answer

len(pdf.pages) counts pages; pdf.pages[0] is the first page; page.page_number is the 1-based number shown in viewers.

count_pages.py
import time
import pdfplumber

t0 = time.perf_counter()
with pdfplumber.open("samples/real/federal-register-2020-17221.pdf") as pdf:
    n = len(pdf.pages)
print(n, "pages, counted in", round((time.perf_counter() - t0) * 1000), "ms")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
15 pages, counted in 6 ms

Counting reads only the PDF’s page tree, which is why 15 pages take a few milliseconds. Characters, lines and images are parsed lazily, the first time you use them on a given page.

Loop over pages

loop_pages.py
import pdfplumber

with pdfplumber.open("samples/bank-statement.pdf") as pdf:
    for page in pdf.pages:
        first = page.extract_text().splitlines()[0]
        print(page.page_number, f"{page.width:.0f}x{page.height:.0f}", "rotation", page.rotation, "|", first)
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
1 595x842 rotation 0 | Riverbend Community Bank
2 595x842 rotation 0 | Riverbend Community Bank
3 595x842 rotation 0 | Riverbend Community Bank

Indexing is 0-based (pdf.pages[2] is the third page) and page_number is 1-based. When you open with pages=[…] (how), page_number still refers to the original document, which keeps log messages and citations correct.

Size, orientation and paper format

page.width and page.height are in points (72 per inch, about 2.83 per millimetre) and describe the page as it is displayed, after rotation:

paper_size.py
import pdfplumber

SIZES = {"US Letter": (612, 792), "A4": (595.28, 841.89), "US Legal": (612, 1008)}

def paper(page):
    w, h = sorted((page.width, page.height))
    for name, (sw, sh) in SIZES.items():
        if abs(w - sw) < 2 and abs(h - sh) < 2:
            return name
    return f"{w / 72:.2f} x {h / 72:.2f} in"

for name in ["invoice", "bank-statement", "real/nics-background-checks-2015-11", "page-boxes"]:
    with pdfplumber.open(f"samples/{name}.pdf") as pdf:
        p = pdf.pages[0]
        orient = "landscape" if p.width > p.height else "portrait"
        print(f"{name.split('/')[-1]:34} {paper(p):16} {orient}")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
invoice                            US Letter        portrait
bank-statement                     A4               portrait
nics-background-checks-2015-11     US Legal         landscape
page-boxes                         8.66 x 12.09 in  portrait

page.rotation is the page’s /Rotate value. A landscape page can be a landscape MediaBox (the NICS report above) or a portrait page rotated by 90°; text extraction differs between the two (rotated pages). The boxes behind width and height (MediaBox, CropBox and others) are described in page boxes.

What is on a page

page.objects groups every object by type; the shortcuts page.chars, page.lines, page.rects, page.curves, page.images and page.annots return the same lists:

page_objects.py
import pdfplumber

with pdfplumber.open("samples/report.pdf") as pdf:
    page = pdf.pages[0]
    for kind, objs in page.objects.items():
        print(f"{kind:6} {len(objs)}")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
char   621
rect   1
line   20

Every property of each type is listed on objects.

Find the page that contains something

find_page.py
import pdfplumber

with pdfplumber.open("samples/report.pdf") as pdf:
    hits = [page.page_number for page in pdf.pages if page.search("Site 30")]
print("'Site 30' appears on page(s):", hits)
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
'Site 30' appears on page(s): [2]

page.search() accepts regular expressions and returns positions as well (search text). For a whole document, stop at the first hit when you only need one page; each search() parses its page.

Pages are objects you can narrow

page.crop(), within_bbox(), outside_bbox(), filter() and dedupe_chars() return a derived page with the same methods, limited to part of the original. They do not copy the PDF, so they are cheap; chain them as needed:

derived_pages.py
import pdfplumber

with pdfplumber.open("samples/report.pdf") as pdf:
    page = pdf.pages[1]
    body = page.crop((0, 60, page.width, page.height - 50)).dedupe_chars()
    text = body.extract_text()
    print(text.splitlines()[:2], "| page_number:", body.page_number, "| bbox:", body.bbox)
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
['Site Result Reading (ppb)', 'Site 15 Pass 4.4'] | page_number: 2 | bbox: (0, 60, 612, 742)

Derived pages keep the original page’s coordinates and page_number (cropping a cropped page).