Document Serialization Architecture#
Docling's serializer system converts a DoclingDocument into text-based output formats (Markdown, HTML, LaTeX, plain text, etc.). The architecture is modular: each output format is its own doc serializer composed of swappable element serializers for specific item types (text, tables, pictures, lists, etc.), and all serializers return a uniform SerializationResult.
Package Location#
All serializer code lives in docling_core/transforms/serializer/ inside docling-core.
Class Hierarchy#
BaseDocSerializer (ABC) ← base.py
└── DocSerializer (Pydantic) ← common.py (holds element-serializer slots)
├── MarkdownDocSerializer ← markdown.py
├── HTMLDocSerializer ← html.py
├── DocTagsDocSerializer ← doctags.py
├── DocLangDocSerializer ← doclang.py
├── PlainTextDocSerializer ← plain_text.py
├── LaTeXDocSerializer ← latex.py
├── WebVTTDocSerializer ← webvtt.py
├── MsExcelMarkdownDocSerializer ← markdown_excel.py
└── AzureDocSerializer ← azure.py
BaseDocSerializer (in base.py) defines the full contract: serialize(), formatting hooks (serialize_bold(), serialize_italic(), etc.), get_parts(), post_process(), serialize_captions(), serialize_footnotes(), and serialize_meta().
DocSerializer (in common.py) is the concrete Pydantic base that all format implementations extend. It holds eight named element-serializer slots :
| Slot | Handles |
|---|---|
text_serializer | TextItem (paragraphs, headings, code, formulas, …) |
table_serializer | TableItem |
picture_serializer | PictureItem |
key_value_serializer | KeyValueItem |
form_serializer | FormItem |
list_serializer | ListGroup |
inline_serializer | InlineGroup |
fallback_serializer | Any other NodeItem |
Each slot accepts any implementation of the corresponding Base*Serializer ABC from base.py . During serialize(), DocSerializer dispatches to the appropriate slot based on isinstance checks .
The meta_serializer slot (optional) handles structured metadata via BaseMetaSerializer .
SerializationResult#
Every serializer — doc-level and element-level — returns a SerializationResult: a model with a text: str field and a spans: list[Span] list. Span wraps the originating DocItem, preserving provenance for downstream uses like chunking. The helper create_ser_result() is the idiomatic way to build results without manually managing span deduplication.
CommonParams#
All format serializers share CommonParams, a Pydantic model that carries cross-cutting options:
labels– allowlist ofDocItemLabelto includelayers–ContentLayerfilterpages– page-number filter (None = all)start_idx/stop_idx– slice-like range over document itemsinclude_formatting/include_hyperlinks– toggle inline markuptraverse_pictures– whether to recurse intoPictureItemchildrenallowed_meta_names/blocked_meta_names– meta field filtering
Format-specific serializers extend CommonParams with their own fields (e.g., MarkdownParams adds image_placeholder, compact_tables, etc.).
Built-in Format Serializers#
| Class | Module | Output format |
|---|---|---|
MarkdownDocSerializer | markdown.py | GitHub-flavored Markdown |
HTMLDocSerializer | html.py | HTML (single-column or split-page) |
DocTagsDocSerializer | doctags.py | DocTags XML (OTSL) |
DocLangDocSerializer | doclang.py | DocLang XML |
PlainTextDocSerializer | plain_text.py | Undecorated plain text |
LaTeXDocSerializer | latex.py | LaTeX |
WebVTTDocSerializer | webvtt.py | WebVTT subtitles |
AzureDocSerializer | azure.py | Azure Document Intelligence JSON |
MsExcelMarkdownDocSerializer | markdown_excel.py | Excel-optimized Markdown |
Swapping an Element Serializer#
Pass any Base*Serializer-compliant object into the constructor slot. Example from the serialization notebook: swap MarkdownDocSerializer's default table serializer for TripletTableSerializer and override the image placeholder:
from docling_core.transforms.chunker.hierarchical_chunker import TripletTableSerializer
from docling_core.transforms.serializer.markdown import MarkdownDocSerializer, MarkdownParams
serializer = MarkdownDocSerializer(
doc=doc,
table_serializer=TripletTableSerializer(),
params=MarkdownParams(image_placeholder="<!-- image -->"),
)
result = serializer.serialize()
Creating a Custom Element Serializer#
Subclass the relevant Base*Serializer from base.py and override serialize(). The notebook shows a AnnotationPictureSerializer that extends MarkdownPictureSerializer to append VLM-generated picture descriptions from item.meta.description:
from docling_core.transforms.serializer.markdown import MarkdownPictureSerializer
class AnnotationPictureSerializer(MarkdownPictureSerializer):
@override
def serialize(self, *, item, doc_serializer, doc, **kwargs):
parent_res = super().serialize(item=item, doc_serializer=doc_serializer, doc=doc, **kwargs)
if item.meta and item.meta.description:
description = f"<!-- Picture description: {item.meta.description.text} -->"
return create_ser_result(
text="\n".join([parent_res.text, description]),
span_source=item,
)
return parent_res
Then inject it:
serializer = MarkdownDocSerializer(
doc=doc,
picture_serializer=AnnotationPictureSerializer(),
)
Similarly, for chunking pipelines, inject a custom table serializer via ChunkingSerializerProvider :
class MDTableSerializerProvider(ChunkingSerializerProvider):
def get_serializer(self, doc):
return ChunkingDocSerializer(
doc=doc,
table_serializer=MarkdownTableSerializer(),
params=MarkdownParams(compact_tables=True),
)
chunker = HybridChunker(serializer_provider=MDTableSerializerProvider())
Entry Points on DoclingDocument#
DoclingDocument exposes convenience methods that wrap serializers:
export_to_markdown()/save_as_markdown()— delegates toMarkdownDocSerializer(see source)export_to_html()/save_as_html()— delegates toHTMLDocSerializer
For full control, instantiate any serializer directly and call .serialize().
Key References#
| Resource | Purpose |
|---|---|
base.py | All Base*Serializer ABCs and SerializationResult |
common.py | DocSerializer, CommonParams, create_ser_result() |
serialization.ipynb | Runnable examples: basic use, swapping serializers, custom serializers |
| Docling serialization concept docs | User-facing conceptual overview |