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

Troubleshooting

pdfplumber error index

Paste the last line of your traceback below. Each message here was triggered on purpose with pdfplumber 0.11.10 and pdfminer.six 20260107, so the wording matches what you see, and each comes with the cause and a tested fix.

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

Find your error

Type or paste part of your error message below to filter the list. Most pdfplumber failures are one of these twenty; each links to a page with the full explanation and tested code. No traceback, just bad output? Jump to the PDF Doctor.

  1. ModuleNotFoundError: No module named 'pdfplumber'

    ModuleNotFoundError

    Cause: pdfplumber is not installed in the interpreter that runs your code.

    Fix: Install through that interpreter: python -m pip install pdfplumber, or %pip install pdfplumber in Jupyter. Details →

  2. AttributeError: module 'pdfplumber' has no attribute 'open'

    AttributeError (Python 3.13+: "consider renaming …"; 3.10–3.12: "partially initialized module …")

    Cause: A file or folder named pdfplumber in your project shadows the library.

    Fix: Rename your pdfplumber.py and delete its __pycache__. Details →

  3. NameError: name 'pdfplumber' is not defined

    NameError

    Cause: The file never ran import pdfplumber.

    Fix: Add the import, or re-run the import cell after a kernel restart. Details →

  4. PdfminerException: seek of closed file

    pdfplumber.utils.exceptions.PdfminerException wrapping ValueError

    Cause: A page was used after the PDF was closed, typically after the with pdfplumber.open(...) block ended.

    Fix: Extract everything inside the with-block (store strings, not pages), or call pdf.close() yourself when done. Details →

  5. PdfminerException: (empty message)

    PdfminerException wrapping pdfminer.pdfdocument.PDFPasswordIncorrect

    Cause: The PDF is encrypted and the password is wrong or missing.

    Fix: Open with pdfplumber.open(path, password="…"). Details →

  6. PdfminerException: No /Root object! - Is this really a PDF?

    PdfminerException wrapping pdfminer.pdfparser.PDFSyntaxError

    Cause: The bytes are not a PDF: an HTML error page saved as .pdf, a failed download, or another format.

    Fix: Check the file starts with %PDF-; re-download it. If it is a damaged PDF, try repair=True. Details →

  7. PdfminerException: Unexpected EOF

    PdfminerException wrapping pdfminer.psexceptions.PSEOF

    Cause: The file is truncated: an interrupted download or upload.

    Fix: Get the complete file. pdfplumber.open(path, repair=True) can sometimes recover the readable pages (needs Ghostscript). Details →

  8. TypeError: 'NoneType' object is not iterable

    TypeError

    Cause: extract_table() found no table and returned None, which the loop then tried to iterate.

    Fix: Check for None (table = page.extract_table(); if table: …), or use extract_tables(), which returns an empty list. Details →

  9. AttributeError: 'NoneType' object has no attribute …

    AttributeError

    Cause: Same pattern: a method returned None (no table, no search match) and the next call assumed a result.

    Fix: Guard the result before using it. Details →

  10. ValueError: vertical_strategy must be one of{lines,lines_strict,text,explicit}

    ValueError

    Cause: A misspelled strategy name, such as "line".

    Fix: Use one of the four names exactly. Details →

  11. ValueError: If vertical_strategy == 'explicit', explicit_vertical_lines must be specified as a list/tuple of two or more floats/ints.

    ValueError

    Cause: The explicit strategy needs at least two lines to make a column.

    Fix: Pass two or more x positions (or objects), or keep another strategy and add explicit lines to it. Details →

  12. ValueError: Table setting 'snap_tolerance' cannot be negative

    ValueError

    Cause: A tolerance or length setting below zero.

    Fix: Use zero or a positive number. Details →

  13. ValueError: Bounding box (0, 700, 612, 800) is not fully within parent page bounding box (0, 0, 612, 792)

    ValueError

    Cause: crop() was given a box that extends past the page (strict mode).

    Fix: Clamp the box to page.bbox, or pass strict=False. Details →

  14. UnicodeEncodeError: 'charmap' codec can't encode character '\uf0b7' in position 0: character maps to <undefined>

    UnicodeEncodeError (when printing, mostly on Windows)

    Cause: The console uses a legacy code page that cannot show the character; pdfplumber extracted it fine.

    Fix: Set PYTHONUTF8=1, or write to a file with encoding="utf-8". Details →

  15. (cid:81)(cid:117)(cid:97)… in extracted text

    Not an exception: wrong output

    Cause: The font has no Unicode mapping.

    Fix: OCR the page, or try chr(N) and verify. Details →

  16. extract_text() returns an empty string

    Not an exception: empty output

    Cause: No text layer: a scanned page, or text drawn as vector outlines.

    Fix: Check len(page.chars) and page.images; run OCR. Details →

  17. Wordsruntogether / S U M M A R Y / TToottaall

    Not an exception: spacing problems

    Cause: Word gaps smaller or larger than x_tolerance, or fake-bold text drawn twice.

    Fix: x_tolerance_ratio, a region-specific x_tolerance, or dedupe_chars(). Details →

  18. RecursionError: maximum recursion depth exceeded

    RecursionError

    Cause: A malformed PDF with circular or very deep object references.

    Fix: Upgrade (0.11.6 handles more of these), then try repair=True. Details →

  19. ImportError: MagickWand shared library not found

    ImportError (pdfplumber older than 0.10.0)

    Cause: Old versions used ImageMagick for to_image().

    Fix: Upgrade: python -m pip install -U pdfplumber. Rendering now uses pypdfium2. Details →

  20. Process killed / MemoryError on a large PDF

    Out of memory

    Cause: Pages and their parsed objects stay cached while the PDF is open.

    Fix: Call page.close() after each page, or process in chunks. Details →

The messages, reproduced

This script triggers each failure on purpose and prints exactly what Python raised. Most errors from inside PDF parsing reach you as pdfplumber.utils.exceptions.PdfminerException, with pdfminer.six’s original exception inside it (e.args[0]). That is the one to inspect when deciding how to handle a failure:

error_catalog.py
"""Trigger common pdfplumber errors on purpose and print the exact messages."""
import io
import os
import subprocess
import sys
import pdfplumber

def show(label, fn):
    try:
        fn()
        print(f"{label}: no error")
    except BaseException as e:   # noqa: BLE001 (we want everything, by design)
        msg = str(e).splitlines()[0] if str(e) else ""
        print(f"{label}:\n    {type(e).__module__}.{type(e).__name__}: {msg[:110]}")
        inner = e.args[0] if e.args and isinstance(e.args[0], BaseException) else None
        if inner is not None:   # pdfplumber wraps pdfminer's exception; show what is inside
            print(f"      wraps {type(inner).__module__}.{type(inner).__name__}")

def closed_file():
    with pdfplumber.open("samples/invoice.pdf") as pdf:
        page = pdf.pages[0]
    page.extract_text()                         # used after the with-block closed the file

def wrong_password():
    pdfplumber.open("samples/encrypted.pdf", password="wrong").pages[0]

def no_password():
    pdfplumber.open("samples/encrypted.pdf").pages[0]

def not_a_pdf():
    pdfplumber.open(io.BytesIO(b"<html>this is a web page, not a PDF</html>")).pages

def truncated():
    data = open("samples/invoice.pdf", "rb").read()
    pdfplumber.open(io.BytesIO(data[: len(data) // 3])).pages[0].extract_text()

def table_none():
    with pdfplumber.open("samples/borderless-table.pdf") as pdf:
        for row in pdf.pages[0].extract_table():   # None: no table found
            print(row)

def bad_strategy():
    with pdfplumber.open("samples/invoice.pdf") as pdf:
        pdf.pages[0].extract_table({"vertical_strategy": "line"})

def one_explicit_line():
    with pdfplumber.open("samples/invoice.pdf") as pdf:
        pdf.pages[0].extract_table({"vertical_strategy": "explicit", "explicit_vertical_lines": [100]})

def negative():
    with pdfplumber.open("samples/invoice.pdf") as pdf:
        pdf.pages[0].extract_table({"snap_tolerance": -1})

for label, fn in [
    ("page used after the file was closed", closed_file),
    ("wrong password", wrong_password),
    ("encrypted, no password given", no_password),
    ("not a PDF", not_a_pdf),
    ("truncated download", truncated),
    ("looping over extract_table() that found nothing", table_none),
    ("misspelled strategy", bad_strategy),
    ("explicit strategy with one line", one_explicit_line),
    ("negative tolerance", negative),
]:
    show(label, fn)

# Printing a PDF symbol on a console that uses the legacy Windows cp1252 encoding
code = "print('\\uf0b7 bullet from a PDF')"
env = {**os.environ, "PYTHONIOENCODING": "cp1252"}
result = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, env=env)
print("printing a PDF symbol on a cp1252 console:\n   ", result.stderr.strip().splitlines()[-1][:110])
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
page used after the file was closed:
    pdfplumber.utils.exceptions.PdfminerException: seek of closed file
      wraps builtins.ValueError
wrong password:
    pdfplumber.utils.exceptions.PdfminerException: 
      wraps pdfminer.pdfdocument.PDFPasswordIncorrect
encrypted, no password given:
    pdfplumber.utils.exceptions.PdfminerException: 
      wraps pdfminer.pdfdocument.PDFPasswordIncorrect
not a PDF:
    pdfplumber.utils.exceptions.PdfminerException: No /Root object! - Is this really a PDF?
      wraps pdfminer.pdfparser.PDFSyntaxError
truncated download:
    pdfplumber.utils.exceptions.PdfminerException: Unexpected EOF
      wraps pdfminer.psexceptions.PSEOF
looping over extract_table() that found nothing:
    builtins.TypeError: 'NoneType' object is not iterable
misspelled strategy:
    builtins.ValueError: vertical_strategy must be one of{lines,lines_strict,text,explicit}
explicit strategy with one line:
    builtins.ValueError: If vertical_strategy == 'explicit', explicit_vertical_lines must be specified as a list/tuple of two or more f
negative tolerance:
    builtins.ValueError: Table setting 'snap_tolerance' cannot be negative
printing a PDF symbol on a cp1252 console:
    UnicodeEncodeError: 'charmap' codec can't encode character '\uf0b7' in position 0: character maps to <undefine

seek of closed file

pdfplumber.open() used as a context manager closes the file when the block ends. Page objects you kept are still linked to it, and the first method that needs to read the file fails. Pull out what you need while the file is open:

keep_open.py
import pdfplumber

# Do all the work inside the with-block, or keep the PDF open yourself.
with pdfplumber.open("samples/invoice.pdf") as pdf:
    texts = [page.extract_text() for page in pdf.pages]

print(texts[0].splitlines()[0])   # plain strings stay usable after closing
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
Hollow Pine Supply Co. INVOICE

Password-protected PDFs

open_with_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 with an empty message; check isinstance(e.args[0], pdfminer.pdfdocument.PDFPasswordIncorrect) to tell it apart from other failures. PDFs with only an owner password (printing or copying restrictions) open without one. The sample is encrypted.pdf, password plumber.

UnicodeEncodeError: ‘charmap’

This one is not pdfplumber’s: it happens when Python prints a character that the console’s encoding cannot represent, such as the private-use bullet that Word exports. Since Python 3.6 the interactive Windows console prints UTF-8, but output that is redirected to a file or pipe, some IDE consoles and scheduled tasks still use the ANSI code page, cp1252 on Western systems. Fixes, in order: set the environment variable PYTHONUTF8=1; write output to files with encoding="utf-8"; or replace private-use characters (text.replace("", "•")).

Catch errors in batch jobs

When processing thousands of files, catch the wrapped exception per file and keep going:

batch.py
import logging
import sys
from pathlib import Path

import pdfplumber
from pdfplumber.utils.exceptions import PdfminerException

logging.basicConfig(format="%(levelname)s %(message)s", stream=sys.stdout)
log = logging.getLogger("batch")

texts = {}
for path in sorted(Path("samples/broken").glob("*.pdf")):
    try:
        with pdfplumber.open(path) as pdf:
            texts[path.name] = "\n".join(p.extract_text() for p in pdf.pages)
    except PdfminerException as e:
        log.warning("%s: %s %s", path.name, type(e.args[0]).__name__, e)

print("read:", sorted(texts))
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
WARNING empty.pdf: PDFSyntaxError No /Root object! - Is this really a PDF?
WARNING html-page.pdf: PDFSyntaxError No /Root object! - Is this really a PDF?
WARNING no-xref.pdf: PDFSyntaxError No /Root object! - Is this really a PDF?
WARNING truncated-33.pdf: PSEOF Unexpected EOF
WARNING truncated-90.pdf: PSEOF Unexpected EOF
read: ['bad-page-stream.pdf', 'bad-startxref.pdf']

Run here on the broken sample files: five fail with a logged reason, two open. One of those two, bad-page-stream.pdf, has an empty page that raises nothing, so count characters per page as well.

The performance guide adds timeouts and memory limits for untrusted or very large files.

Diagnose a PDF

For extraction problems without a traceback (empty text, (cid:x), doubled letters, missing tables), the PDF Doctor checks the file page by page. It runs pdfplumber in your browser; your file is not uploaded.

PDF Doctor

Loads Pyodide + pdfplumber 0.11.10 (about 20 MB, cached after the first visit). Your PDF never leaves this device.
No file yet
x — · top —

Drop a PDF here or choose a sample above.

Text-based PDFs work best. Scanned pages have nothing for pdfplumber to read until OCR adds a text layer.

Text and objects

Hover over text to inspect it.
Run pdfplumber to see extract_text() for this page.
Python for this view