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

Tables

table_settings, explained visually

Every key in table_settings changes one step of the table finder. Below, each option is shown on a table drawn with exactly the defect it fixes, with the default result next to the fixed one.

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

Short answer

Pass a dictionary to any table method. Only the keys you set change; everything else keeps its default:

settings_dict.py
import pdfplumber

with pdfplumber.open("samples/bank-statement.pdf") as pdf:
    page = pdf.pages[0]
    table = page.extract_table({
        "vertical_strategy": "text",      # columns from aligned words, not lines
        "horizontal_strategy": "lines",   # rows from drawn rules
        "snap_tolerance": 4,
    })

print(len(table), "rows x", len(table[0]), "columns; header:", table[0])
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
22 rows x 6 columns; header: ['Date', 'Description', '', 'Debit', 'Credit', 'Balance']

On this statement, the text strategy finds one column too many (the empty third header): settings change how tables are found, not whether the result is right, so check it. The borderless guide shows the reliable fix.

The defaults, printed from pdfplumber 0.11.10 itself:

defaults.py
from dataclasses import asdict
from pdfplumber.table import TableSettings

for key, value in asdict(TableSettings()).items():
    print(f"{key:28} {value!r}")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
vertical_strategy            'lines'
horizontal_strategy          'lines'
explicit_vertical_lines      None
explicit_horizontal_lines    None
snap_tolerance               3
snap_x_tolerance             3
snap_y_tolerance             3
join_tolerance               3
join_x_tolerance             3
join_y_tolerance             3
edge_min_length              3
edge_min_length_prefilter    1
min_words_vertical           3
min_words_horizontal         1
intersection_tolerance       3
intersection_x_tolerance     3
intersection_y_tolerance     3
text_settings                {'x_tolerance': 3, 'y_tolerance': 3}

Where each setting acts

The table finder runs four steps (explained on extract tables). Most settings act in one step; the text_* options act in two:

Step Settings
1. Collect edges vertical_strategy, horizontal_strategy, explicit_vertical_lines, explicit_horizontal_lines, edge_min_length_prefilter, min_words_vertical, min_words_horizontal, text_*
2. Clean up edges snap_tolerance (snap_x_ / snap_y_), join_tolerance (join_x_ / join_y_), edge_min_length
3. Find intersections intersection_tolerance (intersection_x_ / intersection_y_)
4. Read cell text text_* (passed to the word extractor), e.g. text_x_tolerance

When a table comes out wrong, look at the debug picture first (page.to_image().debug_tablefinder(settings)) and ask which step failed: missing edges, edges that do not meet, or text read badly.

inspect_finder.py
import pdfplumber

with pdfplumber.open("samples/invoice.pdf") as pdf:
    finder = pdf.pages[0].debug_tablefinder()   # same settings dict as find_tables()

print(len(finder.edges), "edges")
print(len(finder.intersections), "intersections")
print(len(finder.cells), "cells")
print(len(finder.tables), "table(s)")
print("first edge:", {k: finder.edges[0][k] for k in ("orientation", "x0", "top", "x1", "bottom")})
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
15 edges
54 intersections
40 cells
1 table(s)
first edge: {'orientation': 'h', 'x0': 54.0, 'top': 250.0, 'x1': 558.0, 'bottom': 250.0}

Strategies

vertical_strategy and horizontal_strategy choose where edges come from, independently for each direction.

Strategy Edges come from Use when
"lines" (default) drawn lines and the sides of rectangles and curves the table is ruled or drawn with cell boxes
"lines_strict" drawn lines only; rectangle and curve sides are ignored shaded backgrounds or decorative boxes create false cells
"text" words that line up: shared left edges, right edges or centres for columns; each text line’s top and bottom for rows there are no lines in that direction
"explicit" only the lines you list in explicit_vertical_lines / explicit_horizontal_lines you know where the boundaries are

A table with no lines at all needs "text" (or "explicit") in both directions. Crop to the table first, otherwise headings and footnotes join in:

text_strategy.py
import pdfplumber

with pdfplumber.open("samples/borderless-table.pdf") as pdf:
    page = pdf.pages[0]
    print("lines strategy:", page.extract_table())

    table_area = page.crop((50, 118, 590, 250))   # keep the title and footnote out
    settings = {"vertical_strategy": "text", "horizontal_strategy": "text"}
    for row in table_area.extract_table(settings)[:4]:
        print(row)
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
lines strategy: None
['Segment Q', '1 2026 Q', '2 2026 Q', '3 2026 Change', 'Q/Q']
['', '', '', '', '']
['Industrial sensors', '12,480', '13,115', '14,002', '6.8%']
['', '', '', '', '']
x — · top —
Borderless table with edges inferred by the text strategy
Edges inferred by the text strategy. Column edges sit on the left edges of right-aligned numbers, which cuts the wider headers (Segment Q | 1 2026 …), and each text line gets edges above and below it, which creates the empty rows. Source: borderless-table.pdf (pdfplumber.dev sample, CC0)

That result shows the text strategy’s two typical side effects: headers split where right-aligned numbers start, and blank rows between lines. The borderless tables guide has a recipe that matches this table exactly.

min_words_vertical and min_words_horizontal

With the text strategy, a column edge needs at least min_words_vertical (default 3) words aligned on it, and a row edge at least min_words_horizontal (default 1). Raise min_words_vertical to stop accidental alignments from becoming columns: on the bank statement, raising it to 5 removed the phantom column at x ≈ 180, but also dropped the header row from the table, and from 7 a real column disappears, so check the result.

Explicit lines

explicit_vertical_lines and explicit_horizontal_lines accept numbers (an x or y position; the line spans the whole page) or objects such as page.lines, page.rects or page.curves (their actual edges are used). Explicit lines are added to whatever the strategy found; with "explicit" they are the only edges in that direction, and you need at least two.

explicit_lines.py
import pdfplumber

with pdfplumber.open("samples/bank-statement.pdf") as pdf:
    page = pdf.pages[0]
    names = ["Date", "Description", "Debit", "Credit", "Balance"]
    header = [w for w in page.extract_words() if w["text"] in names]
    columns = [header[0]["x0"] - 4]
    columns += [(a["x1"] + b["x0"]) / 2 for a, b in zip(header, header[1:])]
    columns += [header[-1]["x1"] + 4]

    settings = {
        "vertical_strategy": "explicit",
        "explicit_vertical_lines": columns,        # numbers: full-height lines
        "horizontal_strategy": "explicit",
        "explicit_horizontal_lines": page.lines,   # objects: the ruled lines as drawn
    }
    table = page.extract_table(settings)
    print(len(table), "rows")
    print(table[0])
    print(table[1])
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
23 rows
['Date', 'Description', 'Debit', 'Credit', 'Balance']
['2026-10-02', 'Pharmacy 24', '96.27', '', '2,353.73']

Here the columns come from the header row and the rows from the ruled lines exactly as drawn. Adding one explicit line to "lines" is also the quick fix for a table that is missing its outer border.

snap_tolerance: misaligned rules

Lines that should be one column boundary are sometimes drawn a few points apart, typically when each row is drawn separately. snap_tolerance (default 3 pt) moves parallel edges that are this close onto a common position.

x — · top —
Imperfect table (snap) with default table settings
Default (3 pt): rules offset by 4 pt stay separate, giving extra, sliver columns. Source: imperfect-tables.pdf (pdfplumber.dev sample, CC0)
x — · top —
Imperfect table (snap) with adjusted table settings
snap_tolerance: 5: the offset rules snap into three clean columns. Source: imperfect-tables.pdf (pdfplumber.dev sample, CC0)

snap_x_tolerance and snap_y_tolerance set the vertical-line and horizontal-line snapping separately. Snapping too far merges real columns that are close together.

join_tolerance: broken lines

Rules are often drawn as several segments with small gaps, especially at the points where they cross other rules. join_tolerance (default 3 pt) joins collinear segments whose gap is no larger than this.

x — · top —
Imperfect table (join) with default table settings
Default: 8 pt gaps at every crossing, so no intersections form and no table is found. Source: imperfect-tables.pdf (pdfplumber.dev sample, CC0)
x — · top —
Imperfect table (join) with adjusted table settings
join_tolerance: 9: segments join into full rules and every cell appears. Source: imperfect-tables.pdf (pdfplumber.dev sample, CC0)

On this table, intersection_tolerance: 5 also works, because the segment ends are within 4 pt of the crossing lines. Joining is the more honest fix when the gaps are in the middle of a line rather than at crossings.

edge_min_length and edge_min_length_prefilter: short and dashed lines

Two length filters exist. edge_min_length_prefilter (default 1 pt, added in 0.11.8) drops tiny segments before snapping and joining. edge_min_length (default 3 pt) drops short edges after joining. Dashed or dotted rules drawn as many short segments fall foul of the first:

x — · top —
Imperfect table (prefilter) with default table settings
Default: 915 dashes of 0.8 pt each are all filtered out before joining. No edges, no table. Source: imperfect-tables.pdf (pdfplumber.dev sample, CC0)
x — · top —
Imperfect table (prefilter) with adjusted table settings
edge_min_length_prefilter: 0.5: dashes survive, join into rules, and the table appears. Source: imperfect-tables.pdf (pdfplumber.dev sample, CC0)

Raise edge_min_length instead when tick marks, underlines or chart axes produce stray edges that break a table into pieces.

intersection_tolerance: lines that stop short

An intersection needs a vertical and a horizontal edge to meet. intersection_tolerance (default 3 pt) lets them miss by up to this much.

x — · top —
Imperfect table (intersection) with default table settings
Default: inner rules stop 6 pt short of the top and bottom border, so those rows become single wide cells. Source: imperfect-tables.pdf (pdfplumber.dev sample, CC0)
x — · top —
Imperfect table (intersection) with adjusted table settings
intersection_tolerance: 7: the corners are found and all 12 cells appear. Source: imperfect-tables.pdf (pdfplumber.dev sample, CC0)

Here are all four cases, measured with the default settings and with the single change shown:

imperfect_tables.py
import pdfplumber

cases = {
    "A misaligned": ((50, 105, 290, 230), {"snap_tolerance": 5}),
    "B broken":     ((320, 105, 560, 230), {"join_tolerance": 9}),
    "C dashed":     ((50, 315, 290, 440), {"edge_min_length_prefilter": 0.5}),
    "D short":      ((320, 315, 560, 440), {"intersection_tolerance": 7}),
}

with pdfplumber.open("samples/imperfect-tables.pdf") as pdf:
    page = pdf.pages[0]
    for name, (bbox, fix) in cases.items():
        region = page.crop(bbox)
        for label, settings in [("default", {}), ("fixed", fix)]:
            table = region.find_table(settings)
            cells = len(table.cells) if table else 0
            header = table.extract()[0] if table else None
            print(f"{name:13} {label:8} {cells:2} cells  {header}")
Outputcaptured · Python 3.13.18 · pdfplumber 0.11.10
A misaligned  default  12 cells  ['Item', None, 'Qty', None, 'Price']
A misaligned  fixed    12 cells  ['Item', 'Qty', 'Price']
B broken      default   0 cells  None
B broken      fixed    12 cells  ['Item', 'Qty', 'Price']
C dashed      default   0 cells  None
C dashed      fixed    12 cells  ['Item', 'Qty', 'Price']
D short       default   8 cells  ['Item Qty Price', None, None]
D short       fixed    12 cells  ['Item', 'Qty', 'Price']

Note case A: the default finds the right number of cells but the wrong shape (extra None columns). Always check the header row, not just whether a table was found.

text_* settings: how cell text is read

Any key starting with text_ is passed, without the prefix, to the word extractor used for the text strategy and for reading cell contents: text_x_tolerance, text_y_tolerance, text_x_tolerance_ratio, text_keep_blank_chars, text_use_text_flow, text_expand_ligatures, and so on. Use them when cell text has the problems described in extract text, such as merged words. text_tolerance sets both x and y at once.

Complete reference

Key Default Unit What it does
vertical_strategy "lines" – Where column edges come from: lines, lines_strict, text, explicit
horizontal_strategy "lines" – Where row edges come from
explicit_vertical_lines None pt or objects Extra vertical edges (x positions or objects)
explicit_horizontal_lines None pt or objects Extra horizontal edges (y positions or objects)
snap_tolerance 3 pt Snap parallel edges within this distance to one position
snap_x_tolerance, snap_y_tolerance 3 pt Snapping for vertical / horizontal edges separately
join_tolerance 3 pt Join collinear segments separated by up to this gap
join_x_tolerance, join_y_tolerance 3 pt Gaps along horizontal edges (x) / along vertical edges (y)
edge_min_length_prefilter 1 pt Drop edges shorter than this before snapping and joining (0.11.8+)
edge_min_length 3 pt Drop edges shorter than this after joining
min_words_vertical 3 words Text strategy: aligned words needed for a column edge
min_words_horizontal 1 words Text strategy: aligned words needed for a row edge
intersection_tolerance 3 pt Max gap for a vertical and horizontal edge to count as crossing
intersection_x_tolerance, intersection_y_tolerance 3 pt Same, per direction
text_* x_tolerance=3, y_tolerance=3 – Word-extraction options for the text strategy and cell text

Negative values raise ValueError, and an unknown strategy name raises an error listing the four valid ones.

Try every setting live

The Table Settings Lab below runs the real pdfplumber in your browser. It starts on the tutorial’s bank statement: switch vertical_strategy to text and watch the phantom column appear, then tick Click the page to add explicit lines and click between the column headers instead. Open your own PDF with Open PDF; nothing is uploaded.

Table Settings Lab

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

The full-page version with page thumbnails is at /playground/.