Extract tables from a PDF: one table or all of them

Drop a PDF and get its tables back as rows and columns: each table with its caption, page and the headers printed above it.

Drop a PDF or image here, or browse
PDF up to 50 MB · images up to 20 MB
Processed in-flight — temporary uploads are deleted after processing.

What should we pull from this PDF?

Or narrow it down

Or describe it yourself

Ask for all of them, or name the one you need and get only that. Download CSV, JSON or Excel.

By ExtractFox team · Updated · How we test

One report, three tables, two requests

We made a two-page water quality report of the kind utilities publish every year: paragraphs of prose, then three tables of different widths. The values are what trips converters: ND (not detected), <2.0, ranges like 1.2–3.8 and MCL written as TT. The PDF went through this page twice, once with the default (every table) and once with a typed request for one table. Every cell was compared with the table we rendered. Nothing below was edited.

Page 1 of a water quality report: two paragraphs of text, then a seven-column table of regulated contaminants with units, limits, ranges and sources
Page 1: prose, then a 7-column table.
Page 2 of the report with a lead and copper table and an unregulated contaminants table separated by paragraphs
Page 2: two more tables between paragraphs.

1. Every table (the default)

ExtractFox result with three tables labelled Regulated contaminants detected, Lead and copper, and Unregulated contaminants
What this page showed: 3 tables, 79 of 79 cells right, in 5.5 seconds.

Each table came back separately with the caption printed above it, its page and its own headers. The paragraphs between them were left out. Plain numbers are numbers (MCL 10, highest level 0.031); ND–2.1, <2.0–4.1 and TT stayed text exactly as printed, so none of them became a wrong number.

{
  "tables": [
    {
      "caption": "Regulated contaminants detected",
      "page_number": 1,
      "rows": [
        { "Contaminant": "Nitrate (as N)", "Unit": "ppm", "MCL": 10, "Highest level": 3.8, "Range": "1.2–3.8", "Violation": "No", "Likely source": "Fertilizer runoff" },
Start of the downloaded JSON.

2. Just one table, by name

Typed in the box: "Only the lead and copper table, with its own column headers." The result was that table alone, 10 of 10 cells right, in 3.1 seconds. One thing to know: on a typed request the column names come back normalised (action_level rather than Action level); the values are untouched.

contaminant,action_level,90th_percentile,homes_sampled,homes_above_action_level
Lead (ppb),15,3.2,52,1
Copper (ppm),1.3,0.41,52,0
The downloaded CSV, complete.

One report is a demonstration, not a benchmark. It was clean and digitally made; low-resolution scans, headers spanning two rows and tables without any ruling are harder. A cell that can't be read comes back empty rather than guessed.

Why copying the table out isn't enough

This PDF has a text layer, so pdftotext, which gives what copying from a PDF viewer gives, found every value. But it is text, not a table: the columns are lined up with spaces, and since Nitrate (as N) and Erosion of natural deposits contain spaces too, splitting on them breaks the rows. The paragraphs and footers come along, and nothing marks where one table ends and the next begins. On a scan there is no text to copy at all.

 Contaminant                     Unit      MCL      Highest level Range               Violation     Likely source

 Nitrate (as N)                  ppm           10              3.8 1.2–3.8            No            Fertilizer runoff

 Turbidity                       NTU      TT                  0.21 0.03–0.21          No            Soil runoff
pdftotext -layout on page 1, the table part.

Getting the table you want

  • Name the table the way the PDF does: its caption ("Revenue by region"), or its position ("the second table on page 4"). Add the columns you want if you need fewer.
  • Not sure what's in a long PDF? Pick "List of tables" first: you get each table's caption, page, columns and row count, then ask for the one you need.
  • Monthly statements or a table split into several pieces with the same columns: "Merge tables" returns them as one, with the page of each row.
  • For Excel with a sheet per table and numbers ready for formulas, PDF to Excel is the same reader with an Excel-first page; for CSV details (quoting, encoding) see PDF to CSV.

Sources

  • pdftotext 26.10.0 (Poppler) run as pdftotext -layout file.pdf -, October 18, 2026
  • ExtractFox runs on the same PDF, October 18, 2026; script in the repo at scripts/samples/pdf-table-select.mjs, method on how we test

Frequently asked questions

How do I extract a table from a PDF?+

Upload the PDF on this page and click Extract. Every table comes back with its own headers, caption and page; check it on screen and download CSV, JSON or Excel. To get one table, describe it in the box before you click Extract.

Can I extract only one specific table?+

Yes. Type the table's title or position, such as "only the lead and copper table" or "the table on page 3 under Revenue by region". In our test that request returned that table alone, 10 of 10 cells right, from a report with three tables.

How do I extract tabular data from a scanned PDF?+

The same way. OCR is built in, so an image-only PDF is read like one with a text layer. Faint or low-resolution scans are where digits go wrong; an unreadable cell comes back empty rather than guessed.

Can I extract all tables from a PDF at once?+

Yes, that is the default. Each table stays separate: in JSON as its own entry with caption and page, in Excel as its own sheet, and in CSV as rows labelled with the table they came from. A table that continues on the next page is joined into one.

Can I extract tables from a PDF with Python instead?+

Yes. Camelot, pdfplumber and tabula-py work well on PDFs exported from software with ruled tables, and our guide on [extracting tables from PDF with Python](/blog/extract-table-from-pdf-python) compares them. They need tuning per layout and can't read scans; the [API](/developers) returns the same JSON as this page from code.

Will values like ND, <0.5 or 1.2–3.8 survive?+

Yes. Plain numbers come back as numbers you can sum, and anything that isn't a plain number (not detected, below a limit, a range) is kept as text exactly as printed, so it is never turned into a wrong number.

Compare to alternatives

Related extractors