Tools,

How to turn a PDF table into a Markdown table

First, find out which kind of PDF you have. Open it and try to select a number in the table. If you can, the table is already text inside the file, and a short free script turns it into a Markdown table with every cell exact. If you cannot, the PDF is a scan: a photo of a table dressed up as a document. Then OCR has to read every digit, and that is where a wrong number hides.

A digital PDF: free, exact, under a second

Install pdfplumber (pip install pdfplumber; I used 0.11.10), save this as table_to_md.py, and run python table_to_md.py invoice.pdf:

import sys
import pdfplumber

with pdfplumber.open(sys.argv[1]) as pdf:
    for page in pdf.pages:
        for rows in page.extract_tables():
            for i, row in enumerate(rows):
                cells = [(c or "").replace("\n", " ") for c in row]
                print("| " + " | ".join(cells) + " |")
                if i == 0:
                    print("|" + "---|" * len(cells))

My test file was a plumbing invoice we made (13 rows, 5 columns, ruled lines), saved as a PDF from a browser. The script printed all 65 cells exactly, in 0.08 seconds. The start of what it printed:

| SKU | Description | Qty | Unit price | Amount |
|---|---|---|---|---|
| 1001 | Copper pipe 3/4in | 6 | 113.82 | 682.92 |
| 1002 | PEX fitting kit | 7 | 78.64 | 550.48 |
| 1003 | Ball valve 1/2in | 2 | 98.82 | 197.64 |
[...]

pdfplumber finds a table by its ruling lines. It also has settings for tables without lines, which I did not test.

A scanned PDF: where a wrong number hides

I made the same invoice look scanned: tilted 0.8 degrees, with noise, a little blur and JPEG grain, inside a PDF. The script above printed nothing, because a scan has no text to read.

So I ran it through our Apify Actor, PDF OCR to Markdown. It straightens and cleans the page, finds the table by its lines, and reads each cell with Tesseract 5, the free OCR engine. This is the table it returned, 38.6 seconds later, copied from the run:

| SKU | Description | Qty | Unit price | Amount |
|---|---|---|---|---|
| 1001 | Copper pipe 3/4in | 6 | 113.82 | 682.92 |
| 1002 | PEX fitting kit | 7 | 78.64 | 350.48 |
| 1003 | Ball valve 1/2in | 2 | 98.82 | 197.64 |
| 1004 | Teflon tape | 2 | 44.83 | 89.66 |
| 1005 | Water heater flex line |  | 109.30 | 109.30 |
| 1006 | ShutolT valve | 4 | 5.94 | 23.76 |
| 1007 | PVC elbow 2in | 7 | 51.05 | 357.35 |
| 1008 | Drain auger rental | 4 | 12.25 | 49.00 |
| 1009 | Wax ring | 7 | 850 | 3950 |
| 1010 | Supply hose 16in | 10 | 16.17 | 161.70 |
| 1011 | Labor (hours) | 4 | 76.23 | 304.92 |
| 1012 | Trip charge | 10 | 113.80 | 1,138.00 |

32 of the 36 numbers are right. The 4 wrong ones: 550.48 came back as 350.48, the quantity 1 is missing, and 8.50 and 59.50 lost their decimal points. Each of those looks like a real number, which is the worst kind of mistake on an invoice.

So the Actor checks the math in every row (Qty times Unit price against Amount) and the decimal places in every column. The warnings from this run, word for word (row 1 is the header):

Table cell row 6, column 3 has marks that could not be read.
Table row 3: Qty times Unit price does not equal Amount. Check these numbers.
Table row 10: Qty times Unit price does not equal Amount. Check these numbers.
Table cell row 10, column 4 ('850') may have lost its decimal point.
Table cell row 10, column 5 ('3950') may have lost its decimal point.

All 4 wrong numbers were flagged. Words get no such check: "Shutoff valve" came back as "ShutolT valve" with no warning.

What does not work well

For many files, or from your code

PDF OCR to Markdown does both kinds in one run. It reads the PDF's own text where there is some, as the free script does, and uses OCR only on scanned pages. Paste PDF or image links, or upload a file. You get Markdown for the whole file, a pages list with each page's warnings, and a .md file.

Scans that are mostly text, not tables? See how to turn a scanned PDF into Markdown, with two free tools and what broke.

How this was made: Tack, our AI personality, ran pdfplumber 0.11.10 on a Mac and PDF OCR to Markdown (build 0.1.18) on Apify, both on October 29, 2026. The test invoice is one we made, so the true value of every cell is known. The output above is copied from what the script printed and from the run's dataset, with the invoice's heading lines left out. We make the Apify Actor; pdfplumber and Tesseract are free, open source tools that we do not make.