CodeWorlds
Back to collections
Guide17 min readCodeWorlds Team

Docling, turning documents into structure for language models

Docling turns PDF, DOCX and scans into model-ready structure. Version 2.121.0, MIT licence, local layout and table recognition, no file uploads.

Docling, turning documents into structure for language models

Docling is a Python library that converts PDF, DOCX, presentations and scans into a single data structure describing page layout, tables and reading order. The current version is 2.121.0, published on 20 August 2026, the docling-project/docling repository holds roughly 65.3 thousand stars, and the code is released under the MIT licence.

The problem Docling solves

When you build search over your own documents, the first step looks harmless: read the file, extract the text, split it into chunks, compute vectors. Trouble starts with the first financial report or the first technical manual typeset in two columns.

The PDF format does not store paragraphs or tables. It stores drawing instructions: place this string at this point on the page, in this font, at this size. The order of those instructions inside the file is arbitrary and need not have anything to do with the order in which a human reads the page. A library that pulls out plain text returns the strings in file order and its job ends there.

The consequences are predictable and unpleasant. On a two-column page, a sentence from the left column gets glued to the beginning of a sentence from the right. In a results table, a number from the column headed by a year lands next to a label from the neighbouring row, because both were drawn close together. Running headers and page numbers cut into the middle of a paragraph. A footnote interrupts a sentence halfway. The chunk that ends up in your vector store is then correct character by character and completely false as information.

A language model has no way to detect this. It receives text that looks coherent and politely answers on that basis. Errors of this kind do not announce themselves with an exception, only with a quiet, credible-sounding falsehood in the answer. That is why the quality of the document loading layer decides the quality of the whole system more than the choice of embedding model or vector database.

Docling attacks this problem differently from ordinary PDF libraries. Instead of reading only the text stream, it runs a layout recognition model on the page image, which detects blocks: heading, paragraph, table, caption, footnote, footer. Reading order is then derived from those blocks, and tables pass through a separate model that reconstructs the grid of rows and columns.

That reordering of the work has a specific price and a specific payoff. The price is time, because every page goes through a neural network instead of a simple stream walk. The payoff is information that the stream simply does not contain: the knowledge that a given string is a table cell rather than a sentence, and that a page footer does not belong to the paragraph visually preceding it. Without that information no later pipeline step can reconstruct it, because it was already lost when the file was read.

What Docling actually does

Processing a single file proceeds in stages, and the distinction matters, because each stage can be switched on or off separately.

The first stage is the backend, the parser for a specific format. For PDF it extracts text cells together with their position on the page; for DOCX and PPTX it reads the document XML structure. The second stage is the page layout model, in the default configuration a variant named Heron, which splits the page image into regions and assigns them types. The third is deriving reading order from the detected regions. The fourth is TableFormer, the model that reconstructs table structure, with two operating modes, fast and accurate, the default being accurate.

On top of that comes optional OCR for documents without a text layer, served by several engines: EasyOCR, Tesseract in two variants, RapidOCR and the macOS system mechanism. Enrichments run separately and are off by default: code block recognition, mathematical formula recognition with conversion to LaTeX, image classification, image description by a vision model, and chart data extraction.

The result is a DoclingDocument, a Pydantic-based data model in which document elements carry a type, content, position and a place in the hierarchy. From that object you export whatever you need: export_to_markdown, export_to_html, export_to_text, export_to_dict and export_to_doctags, a markup form designed for vision models.

The list of supported input formats in version 2.121.0 is long and includes pdf, docx, doc, pptx, ppt, xlsx, xls, html, md, csv, epub, latex, email, image, audio, video, vtt, the OpenDocument files odt, ods and odp, plus specialised XML schemas: USPTO patents, JATS articles and XBRL financial statements.

Version, licence and project status

The licence is declared consistently, but the packaging hides a detail that will surprise any dependency scanner inspecting archive contents.

The LICENSE file at the repository root contains the MIT licence text with the notice "Copyright (c) 2024 International Business Machines". The PyPI registry reports License-Expression with the value MIT for the docling package. The GitHub programming interface reports MIT for the repository. Three sources in agreement, the matter looks closed.

The surprise arrives with the third check, opening the published package. The archive docling-2.121.0-py3-none-any.whl weighs 5177 bytes and contains exactly four files: METADATA, WHEEL, entry_points.txt and RECORD. It holds not a single line of code and no licence file at all. The source archive has none either, because it consists of .gitignore, README.md, pyproject.toml and PKG-INFO.

The explanation is simple, if not obvious. The docling package has become a metapackage. Its only mandatory dependency is docling-slim[standard]==2.121.0 and that is where the whole codebase lives. The docling-slim package at the same version contains a dist-info/licenses/LICENSE directory entry with the MIT text, as does docling-core at version 2.92.0. Ten extras declared by the metapackage, among them vlm, easyocr, rapidocr, tesserocr, ocrmac, asr and xbrl, map one to one onto extras of docling-slim.

The practical conclusion: if your compliance process reads licence files from installed packages, docling will show up on the list as an entry without a licence, even though it declares MIT in its metadata. Record docling-slim and docling-core in your dependency register as well, because those carry the code and the licence file.

There is a further caveat, more important than the previous one, stated plainly in the project documentation: the MIT licence covers the code, not the models. The weights of the layout recognition model, TableFormer, the OCR engines and the vision models carry their own licences, described alongside the relevant packages and repositories on Hugging Face. For a commercial deployment they must be checked separately, model by model, because the Docling licence says nothing about them.

It also helps to know that the two commands the metapackage provides, docling and docling-tools, are declared in its entry_points.txt file but point at modules living inside docling-slim. Installing the metapackage while skipping dependencies therefore yields commands that break instantly with an import error.

The project status is healthy. The repository is not archived, the last change on the main branch dates from 21 August 2026, there are 4672 forks and 984 open issues. Version 2.121.0 was published on 20 August 2026, and the registry lists 208 releases. The project started in the IBM Research team in Zurich and is now hosted by the LF AI & Data Foundation, which removes part of the risk of depending on a single company.

Installation and first run

Python 3.10 or newer is required. Support for 3.9 was dropped in release 2.70.0.

Code
Bash
# basic installation, pulls in docling-slim[standard]
pip install docling

# convert a file or a URL to Markdown in the current directory
docling https://arxiv.org/pdf/2206.01062

# choose output formats and a target directory
docling report.pdf --to md --to json --output ./out

# faster: no OCR, fast table mode, page limit
docling report.pdf --no-ocr --table-mode fast --page-range 1-20

# more accurate: formula and code block recognition
docling article.pdf --enrich-formula --enrich-code

# a pipeline driven by a vision model instead of separate models
docling report.pdf --pipeline vlm --vlm-model granite_docling

# graphics card, more threads, per-document time limit
docling report.pdf --device cuda --num-threads 8 --document-timeout 120

The first run downloads model weights, so it takes longer than subsequent ones. The --device flag selects the compute device, --num-threads defaults to 4, and --document-timeout sets the limit in seconds for one document. That last parameter matters more in production than it seems, because a single pathological file can occupy a worker for a quarter of an hour.

Configuring the PDF pipeline

From Python you control everything through an options object passed to the converter. The field names below come straight from the PdfPipelineOptions model in version 2.121.0.

Code
Python
from docling.datamodel.base_models import InputFormat
from docling.datamodel.accelerator_options import (
    AcceleratorDevice,
    AcceleratorOptions,
)
from docling.datamodel.pipeline_options import (
    PdfPipelineOptions,
    TableFormerMode,
    TableStructureOptions,
)
from docling.document_converter import DocumentConverter, PdfFormatOption

pipeline_options = PdfPipelineOptions()
pipeline_options.do_ocr = False
pipeline_options.do_table_structure = True
pipeline_options.table_structure_options = TableStructureOptions(
    mode=TableFormerMode.ACCURATE,
    do_cell_matching=True,
)
pipeline_options.do_formula_enrichment = True
pipeline_options.do_code_enrichment = False
pipeline_options.generate_page_images = False
pipeline_options.images_scale = 1.0
pipeline_options.document_timeout = 120.0
pipeline_options.accelerator_options = AcceleratorOptions(
    num_threads=8,
    device=AcceleratorDevice.AUTO,
)

converter = DocumentConverter(
    allowed_formats=[InputFormat.PDF, InputFormat.DOCX],
    format_options={
        InputFormat.PDF: PdfFormatOption(pipeline_options=pipeline_options)
    },
)

result = converter.convert("report.pdf", max_num_pages=200)
print(result.status)
print(result.document.export_to_markdown())

Several things in this example deserve comment. The do_ocr field defaults to true, so if you process only files with a valid text layer, turning it off is the cheapest speed-up you will get. The do_table_structure field is also on by default and accounts for most of the cost on documents with many tables. Switching mode to TableFormerMode.FAST lowers the quality of grid reconstruction, but is often enough for simple tables.

The do_cell_matching setting decides where cell content comes from. A true value matches model predictions back to the text cells in the PDF file. A false value tells the model to determine cells on its own and ignore the file text. The second option saves the day in tables whose source cells are merged across columns, where matching corrupts the output.

The max_num_pages and max_file_size limits are passed to the convert method, not to the pipeline options. The same set of parameters is accepted by convert_all, so limits are set once at the call site rather than in the converter configuration.

The generate_page_images and images_scale fields control whether page images are kept in the result and at what scale. Raising the scale improves recognition of small print, but memory use grows with it, and faster than linearly, because the scale applies to both image dimensions. In parallel processing this is the most common cause of running a worker machine out of memory.

DoclingDocument and chunking

Exporting to Markdown is convenient for a quick look, but for building an index it is better to work on the structure. Docling ships chunkers that cut the document along the heading hierarchy rather than by character count.

Code
Python
from docling.chunking import HybridChunker
from docling.document_converter import DocumentConverter

converter = DocumentConverter()
doc = converter.convert("report.pdf").document

chunker = HybridChunker(
    tokenizer="sentence-transformers/all-MiniLM-L6-v2",
    max_tokens=512,
    merge_peers=True,
)

for chunk in chunker.chunk(dl_doc=doc):
    text_to_embed = chunker.contextualize(chunk=chunk)
    sources = [
        (item.prov[0].page_no if item.prov else None)
        for item in chunk.meta.doc_items
    ]
    print(len(text_to_embed), sources)

The difference between HierarchicalChunker and HybridChunker is significant. The first cuts the document along its structure and pays no attention to length. The second adds tokenizer awareness: it splits chunks that exceed the limit and merges adjacent chunks that are too small when they share the same metadata, as long as merge_peers stays enabled.

The contextualize method is the most underrated part of this interface. It returns the chunk content prefixed with the heading path in which the chunk sits. A paragraph beginning "In the second quarter the result was lower" therefore reaches the embedding together with the fact that it comes from the chapter on the cloud segment. That usually improves retrieval accuracy more than swapping the embedding model.

The meta.doc_items field holds references to the source elements together with the page they came from. That is the raw material for citations in an answer, the mechanism that lets a user verify the source. The ready-made adapters for LlamaIndex and LangChain use exactly these structures, and the output goes into any vector database, Chroma included.

Batch work, offline mode and performance

With a larger number of files two things matter: that one broken document does not halt the run, and that models need not be downloaded on a machine without internet access.

Code
Bash
# download model weights to a chosen directory, for use without a network
docling-tools models download layout tableformer code_formula --output-dir ./models
Code
Python
from pathlib import Path

from docling.datamodel.base_models import ConversionStatus, InputFormat
from docling.datamodel.pipeline_options import PdfPipelineOptions
from docling.document_converter import DocumentConverter, PdfFormatOption

pipeline_options = PdfPipelineOptions()
pipeline_options.artifacts_path = Path("./models")
pipeline_options.enable_remote_services = False
pipeline_options.document_timeout = 90.0

converter = DocumentConverter(
    format_options={
        InputFormat.PDF: PdfFormatOption(pipeline_options=pipeline_options)
    },
)

files = sorted(Path("./documents").glob("*.pdf"))
ok, partial, failed = 0, 0, 0

for result in converter.convert_all(files, raises_on_error=False):
    if result.status == ConversionStatus.SUCCESS:
        ok += 1
        Path(f"./out/{result.input.file.stem}.md").write_text(
            result.document.export_to_markdown(), encoding="utf-8"
        )
    elif result.status == ConversionStatus.PARTIAL_SUCCESS:
        partial += 1
    else:
        failed += 1

print(ok, partial, failed)

The raises_on_error=False parameter changes behaviour fundamentally: instead of an exception on the first failure you get a result object with a status. The status values are pending, started, success, partial_success, failure and skipped. Partial success is the one easiest to overlook, because the document exists and looks reasonable even though some pages failed to process.

Setting artifacts_path points at a directory of previously downloaded weights, and leaving enable_remote_services false guarantees that no stage sends data outside. This is precisely the advantage for which many teams reach for Docling rather than a cloud service: HR files, contracts and medical records never leave your infrastructure, because the models run in place.

The price of that advantage is real and must be stated plainly. Processing is slower than a plain text extraction, because neural network models run on every page. Memory use grows with the resolution of page images. On a CPU, converting a document of several hundred pages with OCR enabled can take tens of minutes. If you process web pages rather than office documents, a cheaper tool is Firecrawl, which needs no vision models for HTML.

Docling versus the alternatives

FeatureDoclingUnstructuredLlamaParseAzure Document IntelligencePyMuPDF
Where processing runslocallylocally or the vendor APIcloud servicecloud servicelocally
Layout recognitionvision modelsmodels plus rulesvendor modelsvendor modelsnone
Table structureTableFormer, two modesyes, depends on strategyyesyesnone
Code licenceMITApache 2.0none, closed sourcenone, closed sourceAGPL 3.0 or commercial
Cost modelyour own computecompute or API feesper-page feesper-page feesyour own compute
Speed per pagelowmediummediummediumvery high

The choice really comes down to three questions. Can the documents leave your network? If not, cloud services are out regardless of quality. Do you care about tables and reading order, or is raw text enough? If plain text from simple files is enough, PyMuPDF will do it hundreds of times faster, only remember its AGPL 3.0 licence or the need to buy the commercial variant. Does your format range go beyond PDF? Here Docling and Unstructured hold a clear advantage over tools specialised in a single format.

Common mistakes

The first is keeping the default configuration at high volume. OCR and full table reconstruction in accurate mode are both on by default. For a corpus of digitally generated files with a correct text layer, disabling do_ocr cuts processing time several times over and breaks nothing.

The second is the absence of a time limit. Without document_timeout set, a single file with a thousand small images can block a worker process for a very long time. In batch processing, a time limit and raises_on_error=False should be in place from day one.

The third is treating the Markdown export as the only output. Markdown loses element positions on the page, so reconstructing citations that point at a page number stops being possible. If you plan to cite sources, keep the DoclingDocument object or the result of export_to_dict, and generate Markdown from it only at the end.

The fourth is splitting text by character count right after conversion. Since Docling reconstructed the document hierarchy, cutting every thousand characters throws that work away. Use HybridChunker and remember contextualize, because without that method a chunk loses its heading path.

The fifth is installing without the extra you need. The metapackage pulls in the standard variant, while OCR engines and vision models sit in extras, among them easyocr, rapidocr, tesserocr and vlm. Calling --pipeline vlm without the matching extra ends in an import error rather than a readable message about a missing dependency.

The sixth is assuming that because the code is MIT, the whole deployment is too. Model weights carry their own terms and for commercial use they have to be reviewed one by one. This is the most common oversight in compliance audits covering tools of this class.

The seventh is ignoring the partial success status. Code that only checks equality against the failure status will let through half-processed documents, which later feed the index with holes in the content.

FAQ

Does Docling send documents to the cloud?

Not unless you enable it. The layout, table and OCR models run locally, and leaving enable_remote_services false blocks any stage that would reach an external service. Weights can be downloaded in advance with docling-tools models download and pointed at through artifacts_path, which allows work on a network cut off from the internet.

How fast is Docling compared with a plain PDF reader?

Clearly slower, and it has to be, because it runs neural network models on every page. A library that pulls out the text stream alone is orders of magnitude faster, but returns content in file order with no table structure. The largest time savings come from disabling do_ocr for digital documents and from TableFormerMode.FAST for simple tables. Next in line are limiting the page range and giving up on keeping page images in the conversion result.

Does the MIT licence cover the models as well?

No. The MIT licence applies to the Docling code. The weights of the layout recognition model, TableFormer, the OCR engines and the vision models carry their own terms, described alongside the relevant packages and repositories. For a commercial deployment each model in use has to be checked separately.

Why does the docling package contain no licence file?

Because from a certain release onward it is a metapackage. The archive docling-2.121.0-py3-none-any.whl is 5177 bytes and holds metadata only, with no code and no licence file, and its sole mandatory dependency is docling-slim[standard]==2.121.0. It is docling-slim that carries the code and the LICENSE file with the MIT text.

Does Docling replace a vector database or an application framework?

No. Docling finishes its work at the document structure and the split into chunks. Embeddings, the index and the retrieval logic are built separately, for example on LlamaIndex or LangChain, and the chunks live in a vector database of your choice.

When is it not worth reaching for?

When documents are simple and digital and throughput is what counts. When the source is web pages, because HTML needs no vision models. When you lack the compute for batch processing and you are allowed to send files outside. In those three cases a simpler or cloud-based tool gives a better cost to effect ratio.

Documentation lives on the project site, the source code in the GitHub repository, and a description of how the models work in the technical report.

Read next

We use cookies to enhance your experience on the site