Document ExtractionOct 17, 2026By Toolverly Editorial

pdfplumber vs PyMuPDF for Invoice Extraction: Reading Order Before Totals

Three synthetic PDF pages show how reading order and image-only documents change an invoice extraction pipeline built with pdfplumber or PyMuPDF.

Hands filling in and signing a paper document with a pen.

Choose pdfplumber for a Python invoice-extraction workflow that needs inspectable text positions and table extraction from born-digital PDFs. Choose PyMuPDF when your application also needs PDF rendering and a broader document-processing interface, provided its licensing fits the deployment. In either case, establish reading order and detect image-only pages before trusting a total.

Our three-page synthetic fixture exposed both problems. On a page whose amounts were drawn before their labels, default PyMuPDF text extraction did not match our line-based TOTAL parser; sorted extraction and pdfplumber’s default text did. Both tools returned no text for the raster-only control. These results justify a staged extraction pipeline, not a claim that either library understands invoices automatically.

Compare the extraction contract

Library What this test exercised Observed outcome Decision boundary
pdfplumber Default text plus line-based table extraction Totals on both text pages; one table on the gridded page Useful when inspecting characters, geometry and table rules
PyMuPDF Default versus sorted text, plus page rendering Sorting recovered the second page’s total for our parser Useful when rendering and extraction share an application

pdfplumber

The project’s documentation describes character-level information, text extraction and configurable table detection, with an MIT license. We tested pdfplumber 0.11.10 using extract_text() and extract_tables() without custom table settings.

The first page had a ruled table with columns Item, Qty, Unit and Line total. It contained Service / 2 / 50.00 / 100.00 and Support / 1 / 25.00 / 25.00. pdfplumber recovered that table’s header and two rows. The page’s separate TOTAL 125.00 was also present in the text. On the second text page, it presented the label and value together so our exact TOTAL-line regex captured 210.00.

This is useful evidence for this particular machine-generated layout. It does not establish accuracy on borderless tables, merged cells, rotated text, unusual fonts or real suppliers’ documents. We did not compare pdfplumber’s table output against PyMuPDF’s table extraction, so there is no supported table-accuracy winner.

PyMuPDF

PyMuPDF’s text recipes distinguish extracted order from a visually useful reading order. In our 1.28.2 build, default get_text() on page two followed the content sequence sufficiently to put the amounts before their labels. The parser looking for a line beginning TOTAL followed by an amount found nothing. get_text(sort=True) produced a matching TOTAL line.

That is a concrete reason to test your parser and extraction settings together. The number 210.00 was not missing from the default text; our downstream rule could not associate it correctly. Switching libraries is not the only fix. Sorting, coordinate-based association or a better layout model may be appropriate, depending on the document family.

PyMuPDF also rendered the fixture for inspection and created the raster-only control. Its licensing page offers AGPL and commercial routes. Confirm the applicable terms for your own application and dependencies before embedding it; we did not obtain a commercial quote or determine a reader’s legal obligations.

The three pages and actual outputs

Page one was a born-digital, gridded two-line invoice with total 125.00. Page two visually showed SUBTOTAL 200.00, TAX 10.00 and TOTAL 210.00, but the PDF drawing commands wrote each value before its label. Page three was a raster rendering of page one inserted as a single image, with no text layer.

For page one, all three tested text modes captured 125.00. For page two, PyMuPDF default captured no total; PyMuPDF sorted and pdfplumber default captured 210.00. For page three, all three returned no text and no total. The raster page visibly contained 125.00, so treating an empty extraction as a zero-value invoice would have been a serious workflow error.

The reversed-order page can be reproduced with this small ReportLab fixture:

from reportlab.pdfgen import canvas
c = canvas.Canvas('order.pdf', pagesize=(612, 792))
c.setFont('Helvetica', 12)
for y, label, amount in [
 (640, 'SUBTOTAL', '200.00'),
 (610, 'TAX', '10.00'),
 (580, 'TOTAL', '210.00')]:
 c.drawString(300, y, amount)  # value first in content stream
 c.drawString(50, y, label)
c.save()

Our extraction rule and two PyMuPDF settings were:

import re, pymupdf
page = pymupdf.open('order.pdf')[0]
pattern = r'^TOTAL\s+([0-9]+[.][0-9]{2})\s*$'
for sorted_text in (False, True):
 text = page.get_text(sort=sorted_text)
 print(re.findall(pattern, text, re.M))
# Default: []
# Sorted: ['210.00']

The full three-page run used ReportLab 5.0.1 and retained the generated PDF, page images, complete extracted strings and table output. The short reproduction above isolates the reading-order issue; it does not recreate the table or raster control.

A pipeline decision, not an OCR score

Prefer pdfplumber when you want to inspect why a text/table rule did or did not associate fields. Prefer PyMuPDF when rendering and extraction belong in the same application and its licensing route is appropriate. Either choice needs document-family fixtures, an empty-text branch and an exception path for ambiguous totals.

PyMuPDF documents an OCR route using separately installed Tesseract. We did not run it. Our raster result demonstrates the need to route the document elsewhere, not that OCR is unavailable or unsuccessful. A production acceptance rule should also reconcile extracted line amounts with a verified total and preserve the original document for review; our fixture does not validate tax, currency or payment instructions.

This is extraction code rather than a hosted seat plan. pdfplumber’s MIT distribution and PyMuPDF’s license choices are not comparable SaaS subscription prices. For manual document changes see PDF editors; for approvals and payment handoff see accounts payable automation.

Methodology and limitations

Toolverly Editorial generated and executed this fixture on 17 October 2026 on macOS arm64 with Python 3.14.6, pdfplumber 0.11.10, PyMuPDF 1.28.2 and ReportLab 5.0.1. We visually inspected the pages and retained outputs. Documentation, OCR prerequisites and license sources were checked the same day.

We did not test real supplier invoices, OCR, encrypted PDFs, extraction speed, production accounting controls or a commercial SaaS product. Three synthetic pages are functional examples, not a representative accuracy benchmark. No financial data or customer documents were used.

By Toolverly Editorial. AI assistance was used to prepare code, research and prose; the local examples were executed and their outputs reviewed before publication.

Photo by Jakub Żerdzicki on Unsplash. Read about our publication or send a correction.