Back to blog

Docling vs Unstructured for PDF to Markdown (Tested)

Docling 2.141 and the open-source Unstructured library, in its fast, hi_res and ocr_only strategies, run on the same five PDFs. Tables, OCR, forms, speed and setup compared, with verbatim output.

In our tests Docling rebuilt 20 of the 22 rows of a dense government table exactly, and the open-source Unstructured library got 10 of 22 at best, in its hi_res strategy. Unstructured’s default strategy for born-digital PDFs, fast, was 8 to 24 times faster than Docling on the paper, the table and the form, but it returns no tables and no OCR, and on our 60-page report it returned nothing at all. Docling also gave the cleaner result on a scanned page. Unstructured’s built-in Markdown export writes every title as a level-one heading and tables as HTML. Docling is MIT-licensed and Unstructured is Apache 2.0, so both can be used commercially.

We ran Docling 2.141.0 with default settings and Unstructured 0.27.33, released on October 22, 2026, in each of its PDF strategies, on the same five PDFs on one Mac. Unstructured also sells a hosted API, which we describe from its documentation but did not test. Our tested roundup of PDF to Markdown converters did not include Unstructured; this post uses the same files. Disclosure: Parsebridge, which publishes this blog, sells a hosted Docling API.

Docling vs Unstructured at a glance

DoclingUnstructured (open-source library)
MaintainerStarted at IBM Research, hosted by the LF AI & Data FoundationUnstructured Technologies, which also sells a hosted API
LicenseMITApache 2.0
Installpip install doclingpip install "unstructured[pdf]", plus the Tesseract binary for hi_res and ocr_only
Python3.10 or newer3.11 to 3.13
How it reads a PDFLayout model, TableFormer table model and OCR, on PyTorchFour strategies: fast (PDF text layer only), hi_res (YOLOX layout model, Table Transformer and Tesseract OCR), ocr_only (Tesseract only) and auto, the default, which picks one of them
Install, measured1.4 GB environment plus 0.5 GB of models1.8 GB environment plus 0.3 GB of models, plus Tesseract
Cold start, measured8.2 s2.5 s (fast), 7.5 s (ocr_only), 10.2 s (hi_res)
60-page report, measured230 to 239 sNo text (fast); 225 s (ocr_only, which auto picked) and 237 to 238 s (hi_res), both by OCR
TablesMarkdown tables rebuilt cell by cellHTML <table> blocks from hi_res with infer_table_structure=True; plain text in the other strategies
Multi-column pagesRead in column orderRead in column order in fast and hi_res
OCR for scanned PDFsBuilt in, on by defaultTesseract in hi_res and ocr_only; auto picks ocr_only when a PDF has no text layer
Main outputA document model, exported to Markdown, HTML, JSON, DocTags and moreA list of typed elements (titles, text, list items, tables) with page and position metadata; elements_to_md() turns them into Markdown
Input formatsPDF, DOCX, PPTX, XLSX, HTML, images, audio, LaTeX, email and morePDF, Word, PowerPoint, Excel, HTML, email, EPUB, images, plain text and more

How Unstructured turns a PDF into Markdown

Unstructured’s job is to split documents into elements for retrieval pipelines. partition_pdf() returns a list of Title, NarrativeText, ListItem, Table and other elements, each with its page number and coordinates. How it finds them depends on the strategy argument:

  • fast reads the PDF’s text layer with pdfminer.six. It loads no models and runs no OCR.
  • hi_res renders each page, finds regions with a YOLOX layout model, fills them with text from the PDF and from Tesseract OCR, and, with infer_table_structure=True, rebuilds tables with Microsoft’s Table Transformer. In the installed source, the cell text for those tables comes from running Tesseract on an image of the table, even when the PDF has a text layer.
  • ocr_only renders each page and runs Tesseract, with no layout model.
  • auto, the default, picks hi_res when you ask for table structure or image extraction, fast when the PDF has extractable text, and ocr_only otherwise.

The library has a Markdown export, elements_to_md() in unstructured.staging.base. We read its source. It writes every Title element as a level-one heading (# ), writes a table’s text_as_html metadata as raw HTML when there is one, writes every other element as plain text, and joins the elements with a single newline. List items get no - marker, and a Markdown renderer will merge consecutive paragraphs, since there are no blank lines between them. We used this function for all Unstructured output in this post.

Two setup details caught us. pip install "unstructured[pdf]" does not install Tesseract, and without it hi_res failed even on a born-digital page, with TesseractNotFoundError. We installed Tesseract 5.5.3 and Poppler with Homebrew; the ocr_only code path also calls Poppler’s pdfinfo to count pages. And the library sends usage telemetry to packages.unstructured.io when it is imported and when you partition a file. Setting DO_NOT_TRACK=1 or SCARF_NO_ANALYTICS=1 turns that off, and we set it for the timed runs.

How we tested

We used the five PDFs from the roundup, each stressing one thing:

  • Paper: PubTables-1M, a 10-page, two-column research paper with four tables (CC BY 4.0).
  • Table: Table 1 from page 31 of the EIA Short-Term Energy Outlook, October 2026, with 15 numeric columns under year and quarter headers (US government work, public domain).
  • Scan: our scanned order fixture, an image-only page with a small table.
  • Form: page 1 of the 2026 IRS Form W-4 (public domain).
  • Report: the full 60-page EIA Short-Term Energy Outlook, for timing.

The machine was a Mac mini with an Apple M4 and 16 GB RAM, on macOS 26.6 and Python 3.12.15. Each tool had its own virtual environment. Docling ran as DocumentConverter() with no options; it picked RapidOCR on PyTorch for OCR and ran its models on the Apple GPU. Unstructured ran as partition_pdf(filename=..., strategy=...) once per strategy, with infer_table_structure=True for hi_res and no other options, followed by elements_to_md(). We also ran the default auto strategy without timing it. It chose fast for the paper, the table and the form, and ocr_only for the scan and the report; its output matched those strategies byte for byte.

Each document was converted twice in one process, and we report the second run. “Cold” is a fresh process converting the one-page table PDF, including imports and model loading, with models already downloaded. We started each timed pass only when the one-minute load average was below 3; the readings taken during the passes stayed under 3.4. Unstructured ran with DO_NOT_TRACK=1, and its times cover partition_pdf() and elements_to_md(). Every quality judgment below comes from reading the saved output against the PDF page. Snippets are verbatim; long rows are cut after a few columns and padding spaces are collapsed.

SecondsColdPaper (10 p.)Table (1 p.)Scan (1 p.)Form (1 p.)Report (60 p.)
Docling8.27.24.43.83.9230.4
Unstructured fast2.50.90.40.0 (no text)0.20.1 (no text)
Unstructured hi_res10.228.75.51.34.3236.6
Unstructured ocr_only7.542.15.51.04.4224.7

Results on the same PDFs

Dense table

Docling returned a 15-column Markdown table, combined the year and quarter headers into labels like 2025 - Q1, and got 20 of the 22 data rows exactly right. It merged the last two rows, heating and cooling degree-days, into one row with two numbers per cell:

| | 2025 - Q1 | 2025 - Q2 | 2025 - Q3 | ...
| U.S. Heating Degree-Days ................... U.S. Cooling Degree-Days .................... | 2,104 53 | 436 464 | 55 902 | ...

Unstructured hi_res returned one HTML table. After dropping two empty columns it added, 10 of the 22 rows had all 15 values in the right order. The header came from OCR and lost the quarter labels:

<tr><th>Qt |</th><th>a2 |</th><th>Os f</th><th>a4</th><th>| at</th><th>| 2</th>...

Several row labels were shifted onto the row above, three rows came out as one cell holding the label and all 15 values, the economic rows put each value and its percent change in the same cell, and some numbers were misread (24429 for 24,429, 18.423 for 18,423, 44 for 4.4). It merged the two degree-day rows the same way Docling did:

<tr><td>(billion cubic feet per day) «00... Coal (b)</td><td>110.1</td><td>78.1</td>...
<tr><td>Real Gross Domestic Product (billion chained 2017 dollars - SAAR)... Percent change from prior year ............</td><td>23,548 2.0</td><td>23,771 2.1</td>...

The fast strategy, which auto picks for this page, returned no table. Each row label became a level-one heading, followed by its values one per line:

# Crude Oil Production (a) (million barrels per day) ........................
13.32
13.58
13.82

Two-column paper

All three runs followed the two columns and kept the sections in order, including 9.1 to 9.3 on the last page. Docling turned the section titles into ## headings. Unstructured hi_res made them # headings; fast left “1. Introduction” and the other section titles as plain text and made a level-one heading of some table cells and of the second half of a hyphenated word (# tradictory feedback during training...).

On the tables, fast printed each cell on its own line. hi_res returned all four tables as HTML. Table 2 and Table 4 had every value in place, with OCR slips in the headers (Acccont, GriTScone). In Table 1 the check marks came out as S88, KTR and vt. In Table 3 the two table-detection rows were missing, and two values carried OCR noise:

<tr><td rowspan="2">TSR+FA</td><td>FasterR-CNN</td><td>0.722</td><td>0.815</td><td>= 0.785</td><td>(0.762</td></tr>
<tr><td>DETR</td><td>0.912</td><td>0.971</td><td>0.948</td><td>0.942</td></tr>

Docling kept all four rows of Table 3 but shifted the last column and merged the two TSR + FA rows:

| TD | Faster R-CNN | 0.825 | 0.985 | 0.927 | 0.866 0.981 |
| | DETR | 0.966 | 0.995 | 0.988 | 0.762 |
| TSR + FA | Faster R-CNN DETR | 0.722 0.912 | 0.815 0.971 | 0.785 0.948 | 0.942 |

Docling got Tables 1, 2 and 4 right apart from one header cell in Table 2, and left an <!-- image --> placeholder for each figure. Unstructured hi_res ran OCR on the figures and added their contents as text lines such as Table Functional Analysis aries Adjusted | 2 Ratio (APR) Oral roiene stata Gord ora, and it wrote the rotated arXiv label in the margin as one character per line.

Scanned page

Docling ran OCR with no extra install and returned the full text and a correct table. It split “Total:” and “CHF 42.00” into two paragraphs and joined the three address lines into one:

| Item | Quantity | Amount |
|---|---|---|
| Archive folders | 3 | CHF 18.00 |
| Storage boxes | 2 | CHF 24.00 |

Unstructured ocr_only, which auto picks for a page without a text layer, read every word and number correctly, but put the table on one line and made most lines level-one headings:

# Example Research Lab 42 Sample Street
# Zurich, Switzerland
Item Quantity Amount Archive folders 3 CHF 18.00 Storage boxes 2 CHF 24.00
# Total: CHF 42.00

Unstructured hi_res kept the address lines apart and returned the table as HTML, but lost both quantities and misread two headers:

<table><thead><tr><th>tem</th><th>uartty</th><th>Amount</th></tr></thead><tbody><tr><td>Archive folders</td><td></td><td>CHF 18.00</td></tr>...

The fast strategy returned an empty string, since it does not run OCR.

Form

Docling returned the whole W-4 body with step headings and the checkboxes as - [ ] items. It put the Step 3 and Step 4 headings next to each other, placed the “Exempt from withholding” label inside Step 4, moved the Social Security number box to the end and left out the page footer.

Unstructured fast returned every line of the form in reading order, including the signature line and the footer. Field labels such as # Address and # Last name became level-one headings, there was no checkbox markup, and dot leaders came out as one . per line.

Unstructured hi_res read parts of the form as tables. The Step 1 box and the Step 3 and Step 4 area came out as HTML tables whose text was cut at the cell edges (tep laim ependent Other redits for “Step 3: Claim Dependent and Other Credits”). The step headings then appeared a second time as plain text, the checkboxes came out as LJ [], and the signature line was missing.

Speed and footprint

Unstructured fast loads no models, and on born-digital pages it was the quickest run in this test: 0.35 seconds for the table and 0.16 for the form, against 4.4 and 3.9 seconds for Docling, and 0.9 seconds for the 10-page paper, against 7.2. hi_res was close to Docling on the table and the form, faster on the scan, and took 28.7 seconds on the paper, four times as long as Docling.

On the 60-page report, fast returned an empty string in 0.06 seconds. Before it applies a strategy, partition_pdf() checks whether a PDF is “too complex for text extraction”. Page 29 of the report, a page of charts, has 14,963 graphics operations and 131 text operations, which is over the library’s limits of 10,000 operations and a ratio of 20 to 1. That one page made the library skip the text layer for the whole file, and fast uses nothing else. The library logs this at the INFO level, which Python hides by default; we only saw it with debug logging turned on. hi_res and ocr_only ran OCR on all 60 pages instead and took 237 and 225 seconds. The default auto picked ocr_only for the report for the same reason. Docling took 230 seconds, just under four seconds per page.

A fresh process converted the one-page table in 2.5 seconds with fast, 7.5 seconds with ocr_only and 10.2 seconds with hi_res, including imports and model loading. A fresh Docling process needed 8.2 seconds.

The Docling environment was 1.4 GB, including PyTorch, plus 0.5 GB of layout and table models downloaded on first use. The Unstructured environment with the pdf extra was 1.8 GB, including PyTorch, ONNX Runtime, spaCy and the Google Cloud Vision client, plus 0.3 GB for the YOLOX and Table Transformer models it downloaded on first use of hi_res. Tesseract and Poppler came on top, from Homebrew.

The hosted Unstructured API

We did not test it. Unstructured’s open-source overview says the library is “designed as a starting point for quick prototyping” and “not designed for production scenarios”, and lists “significantly decreased performance on document and table extraction” compared with its hosted product. On the first partition call in each process, the library prints a notice that points to that product. The hosted partitioning docs list Auto, Fast, High Res and VLM strategies, where VLM uses a vision language model.

The pricing page lists a free plan with 10,000 pages to start, pay-as-you-go at $0.015 per page after that, which is $15 per 1,000 pages, and custom Business plans with dedicated or VPC deployment. The results above are for the open-source library only and say nothing about the hosted product’s output.

Code for both

Docling:

pip install docling
docling convert report.pdf --to md --output out/
from docling.document_converter import DocumentConverter

converter = DocumentConverter()  # reuse it; model loading is the slow part
markdown = converter.convert("report.pdf").document.export_to_markdown()

Unstructured:

pip install "unstructured[pdf]"
brew install tesseract poppler   # or apt-get install tesseract-ocr poppler-utils
export DO_NOT_TRACK=1            # optional: turns off usage telemetry
from unstructured.partition.pdf import partition_pdf
from unstructured.staging.base import elements_to_md

elements = partition_pdf(filename="report.pdf", strategy="hi_res", infer_table_structure=True)
markdown = elements_to_md(elements)

Which to pick

Pick Docling when you need Markdown with tables you can use, or when your PDFs mix born-digital and scanned pages. On these files Docling gave the better result on the table, the paper and the scan. On the form, Unstructured fast kept more of the text in order, with less structure. Plan for about four seconds per page on long documents on a machine like this. Our Docling OCR guide covers how its OCR defaults work.

Pick the Unstructured library when you need typed elements with page and position metadata for a retrieval pipeline more than clean Markdown, when you already use it for Word, HTML or email files, and when your PDFs are mostly born-digital text without important tables. Its fast strategy is quick for that. Set the strategy yourself, check long PDFs for empty output, and install Tesseract if you use hi_res or ocr_only. For another fast option on born-digital PDFs that also returns Markdown tables, see our test of Docling vs PyMuPDF4LLM.

Both tools made mistakes on these five PDFs. Run your own worst three or four pages through both before choosing.

Parsebridge

Parsebridge is a hosted Docling API. You send a PDF and get Markdown back without running Docling yourself. It runs Docling 2.124 inside docling-serve 1.32.0, with TableFormer in accurate mode, and turns OCR on when it detects scanned pages. It accepts PDFs only, up to 50 MB. New accounts get 50 free pages once, and plans are $17 a month for 5,000 pages, $79 for 60,000 and $259 for 300,000. Parsebridge does not run Unstructured.

To see Docling’s output on one of your own PDFs first, the free PDF to Markdown converter converts the first page of a PDF up to 10 MB without an account, five times per hour.