Extracting tables from PDFs and spreadsheets for RAG
Ask any RAG pipeline builder which content type causes the most retrieval failures and a lot of them will say tables. A paragraph survives bad extraction gracefully — a jumbled sentence is still mostly readable. A table doesn’t: strip out the column alignment and you’re left with a wall of numbers and labels in an order that means nothing without the grid they came from.
Why tables break naive extraction
A table’s meaning lives entirely in its two-dimensional layout — which cell is in which row, which row belongs under which column header. Most “extract the text” libraries only understand one-dimensional text: a stream of characters in reading order. Fed a table, they have to guess how to flatten two dimensions into one, and the guess is often wrong: cells read top-to-bottom instead of left-to-right, headers separated from the rows they label by hundreds of characters, merged cells duplicated or dropped entirely.
The result is exactly the kind of number-soup that’s worse than an error — it looks like extracted text, gets chunked and embedded like extracted text, and only fails visibly when someone asks a question whose answer was in row 14 of a table that got scrambled into row 3’s neighborhood.
What “extraction” for a table actually means here
It’s worth being precise about what txtfetch does and doesn’t give you.
/v1/extract returns a single flat extracted_text string — there’s no
structured rows/columns object, no JSON representation of cell
boundaries. What Tika does well is flatten a table into that string in
row-major reading order, keeping each row’s cells together and headers
attached to the data below them, instead of scattering them across the
output:
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@quarterly-report.pdf
{
"status": "success",
"extracted_text": "Region\tQ1\tQ2\tQ3\nNorth America\t3.1M\t3.6M\t4.2M\nEMEA\t1.8M\t2.0M\t2.3M..."
}
Spreadsheets go through the same path — a sheet’s rows and columns come back
as linear text in the same shape, so an XLSX model and a
PDF table both resolve to the same kind of extracted_text
your pipeline already knows how to handle:
curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/models/forecast.xlsx" \
-H "Authorization: Bearer $TXTFETCH_KEY"
If your pipeline needs genuinely structured, per-cell output — a JSON object
per row, typed columns — that’s a different problem than text extraction,
and it’s not something /v1/extract claims to solve. What it solves is
getting the table’s content into linear text without scrambling row/column
correspondence, which is the part that actually breaks most pipelines.
Chunking tabular text so retrieval survives
Once a table is flattened, the chunking decision matters more than usual. Splitting a table in the middle of its rows — the same fixed-size cut you’d apply to prose — divorces a row from its header and makes the chunk unanswerable on its own. Two things help:
- Keep small tables whole. A table under a few hundred tokens should usually be its own chunk rather than getting split, so the header stays attached to every row.
- Repeat the header row into each chunk of a large table. For tables too big to keep whole, prefix every chunk with the header row’s text so each chunk is self-describing instead of assuming the header survived from a previous chunk.
See the chunking strategies guide for the general reasoning; tables are the case where getting this specific decision right matters most.
Tables are common enough inside Office documents that it’s also worth reading the DOCX/PPTX/XLSX parsing guide if spreadsheets and embedded tables inside Word docs are a big share of your document set.
Wrestling with a document set that’s mostly tabular? Get in touch and we’ll set you up with an API key.