spacy-layout: PDF and DOCX Conversion into spaCy Doc Objects
📚 Process PDFs, Word documents and more with spaCy
At a glance
- What is it?
- spacy-layout wraps Docling behind a spaCy pipeline component, turning PDFs and Word files into Doc objects with layout spans, page metadata and pandas tables. It is a thin integration layer, so its limits are Docling's limits.
- Who is it for?
- Adopt spacy-layout if you already run spaCy pipelines and want document layout as spans on a Doc rather than as a separate parsing service. Skip it if you need layout analysis without spaCy, or if you cannot accept Docling as a transitive dependency.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 173 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap spacy-layout fills between a PDF and a spaCy pipeline
spaCy has no built-in reader for PDF or Word files. A typical pipeline starts from plain text or from a Doc you construct yourself, which means anyone running named entity recognition or text classification over contracts, reports or manuals writes the same extraction glue first. spacy-layout is that glue, packaged. It integrates with Docling, described in the README as the component that brings structured processing of PDFs, Word documents and other input formats to a spaCy pipeline, and it returns spaCy's familiar Doc objects with labelled spans for sections and headings.
The intended audience is narrow and identifiable: engineers who already have a spaCy pipeline and want document structure available inside it. The README names two downstream uses, linguistic analysis and chunking for retrieval augmented generation pipelines. If neither describes your work, the package is solving a problem you do not have. If you are building a document parser as a standalone service with no spaCy dependency, this wrapper adds a layer you would have to maintain for no gain.
What a converted document actually contains
Calling the layout object on a path returns a Doc. From there, the README lists four extension attributes on the document: Doc._.layout for layout features, Doc._.pages as a list of tuples pairing a PageLayout with the spans on that page, Doc._.tables as a list of spans, and Doc._.markdown as a string representation of the document. Layout spans live in a SpanGroup at doc.spans["layout"], so they behave like any other spaCy span collection.
Each span carries a label such as "text", "title" or "section_header", a layout extension attribute that includes the bounding box, and a heading attribute described in the README as the closest heading to the span, with the caveat that accuracy depends on document structure. That caveat is doing real work. Heading association is inferred from the parsed structure, not from an explicit outline, so a document with unusual or missing heading hierarchy will produce weaker results and the failure will be quiet.
The markdown attribute is worth noting as a second output format. It gives you a text representation you can store or diff without keeping the original binary around, though the README does not specify how much fidelity that conversion preserves for complex layouts.
Tables are placeholders until you supply a renderer
Table handling is the part of the design most likely to surprise. Tables appear as spans labelled "table", and each one exposes a data attribute holding a pandas DataFrame. But by default the span text is the literal placeholder TABLE. The figures are in the DataFrame, not in the token stream.
That matters if your downstream component reads tokens. A named entity recognizer or text classifier applied to the Doc will see the word TABLE where the numbers should be. The README addresses this directly: you pass a display_table callback to the spaCyLayout constructor, and it receives the DataFrame and returns the string to put in the text. The documented example returns a line listing the column names. The callback is where you decide whether table content enters the text at all, and how.
This is a reasonable split, since flattening a DataFrame into prose is a judgement call the library cannot make for you. It is also a step that is easy to skip during a first pass, which produces a pipeline that silently ignores every table in every document. If your corpus is financial or scientific, check this before anything else.
Installation and the two-line setup
The package requires Python 3.10 or above, stated as a warning in the README. Installation is a single pip command:
pip install spacy-layout
Setup is a blank spaCy model plus the preprocessor. The README's example uses spacy.blank("en") for tokenization, then constructs spaCyLayout(nlp) and calls it on a path. To add trained components afterwards, the README loads en_core_web_trf (installed with python -m spacy download en_core_web_trf), builds a second spaCyLayout around it, and then calls nlp(doc) on the already-created Doc. That two-step order is the pattern to copy: conversion first, pipeline second.
For batches there is layout.pipe, which takes an iterable of paths or bytes and yields Doc objects. The README's example passes a list of three PDF paths. Serialization uses spaCy's DocBin with store_user_data=True, written to disk with to_disk. The README flags a wrinkle here: the custom extension attributes are registered when spaCyLayout is initialized, so loading a DocBin back requires initializing a spaCyLayout instance first, otherwise the attributes cannot be repopulated. The README calls this inelegant and says a future version will improve it. Until then, it is a required line of setup in any code path that reads serialized docs.
Where the abstraction leaks
The clearest limitation is dependency depth. spaCy-layout does not parse documents itself. Docling does. That means the quality of every heading, page box and table you get is Docling's quality, and any parsing bug you hit has to be diagnosed in a codebase this package only wraps. The README also defers the full list of layout labels to Docling's documentation rather than reproducing it, so the set of possible span.label_ values is not knowable from this repository alone. Code that switches on label names is written against an external contract.
The heading attribute carries its own uncertainty, stated plainly in the README: accuracy depends on document structure. There is no confidence score attached to it, so a wrong heading is indistinguishable from a right one at the API level.
The version history suggests the interface is still settling. Three releases shipped between December 2024 and March 2025, and the README itself describes the serialization behaviour as something to be made more elegant later. If you pin to a version, expect the extension attribute registration path to be the thing that changes.
Finally, the package is the wrong tool when spaCy is not part of your stack. If you want markdown or structured JSON out of a PDF and nothing else, you are paying for a tokenizer, a Doc model and a span group you will not use.
Docling directly, and what changes if you skip the wrapper
The obvious alternative is Docling itself, which spacy-layout calls as its parsing backend. The difference in approach is where structure lives. Docling exposes a document model of its own, independent of any NLP framework. spacy-layout converts that model into spaCy's Doc, which means layout regions become spans with token and character offsets into a single text stream, and tables become spans whose data attribute is a DataFrame.
That conversion is the entire value of the package, and it is also its cost. You gain offsets that line up with tokens, a SpanGroup you can filter with spaCy's span APIs, and the ability to run nlp(doc) over the result. You lose direct access to Docling's native representation and you inherit a wrapper that must track Docling's changes. For a team already writing spaCy components, the offset alignment is worth the layer. For a team exporting documents to a database or a vector store, Docling alone gives the same parse without the spaCy dependency.
A second option, for text-only corpora, is to skip layout parsing entirely and extract text with a simpler converter. That is cheaper and faster, and it is the right call when headings and tables do not matter. It stops being the right call the moment you need to know which section a sentence came from.
Licence, maintenance and what to check before adopting
The repository is MIT licensed, which permits commercial and closed-source use with the usual requirement to carry the licence notice. That covers spacy-layout. It does not automatically describe Docling, spaCy or pandas, each of which ships under its own terms, so a dependency audit should look at the full installed set rather than this repository's LICENSE file. Nothing here is legal advice.
Maintenance cost has two parts. The first is upgrade risk inside the package, which the serialization note and the three releases in four months both point to. The second is upgrade risk in Docling, which is where parsing behaviour actually lives and which this package cannot insulate you from. A regression in layout detection will surface as different span labels or shifted bounding boxes in your output, with no change on the spacy-layout side.
Before committing, verify three things against your own documents rather than the README's starcraft.pdf example. Confirm Python 3.10 or above and that Docling installs in your environment. Dump span.label_ values across a sample of your real files to see which layout types you actually get, since the label vocabulary is defined elsewhere. And test the display_table callback on a document with a real table, because the default placeholder will otherwise hide every table from your pipeline without raising an error.
Editorial conclusion
Adopt spacy-layout if you already run spaCy pipelines and want document layout as spans on a Doc rather than as a separate parsing service. Skip it if you need layout analysis without spaCy, or if you cannot accept Docling as a transitive dependency. Verify first that your Python is 3.10 or above, that Docling installs cleanly in your environment, and which layout labels your own documents actually produce, because the README defers that list to Docling's documentation rather than enumerating it.
Community notes