Skip to content

Fields and tables of contents

A field is how Word represents anything it works out for itself: page numbers, a table of contents, cross-references, captions that renumber, dates, and references to document properties. Every one of those is the same feature with a different instruction string.

Warning

This library cannot compute a field result, and no API can change that. A table of contents added here is empty, a PAGE field has no number, and a cross-reference shows nothing, because all three depend on how Word lays the document out. Fields are written asking Word to refresh them when it opens the file — see Getting Word to fill them in.

Adding a field

Paragraph.add_field() takes a field code. The builders in docx.fields write the ones people usually want, so you rarely need to remember the switch syntax:

from docx import Document, fields

document = Document()

paragraph = document.add_paragraph("Page ")
paragraph.add_field(fields.page_number())
paragraph.add_run(" of ")
paragraph.add_field(fields.page_count())

The builders return the instruction string, not the field, so they compose with anything that takes a field code:

Builder Field
page_number() PAGE — the current page
page_count() NUMPAGES — pages in the document
table_of_contents() TOC
cross_reference() REF — text of a bookmark
page_reference() PAGEREF — page a bookmark is on
sequence() SEQ — caption and figure numbering
date() DATE
doc_property() DOCPROPERTY
styleref() STYLEREF — nearest text in a style

An instruction Word understands but no builder covers can be passed directly:

paragraph.add_field(r'INCLUDEPICTURE "logo.png" \d')

A table of contents

from docx import Document, fields

document = Document()

document.add_paragraph("Contents", style="Heading 1")
toc = document.add_paragraph()
toc.add_field(fields.table_of_contents(levels=(1, 3)))

document.add_page_break()
document.add_heading("Introduction", level=1)
document.add_paragraph("...")

document.settings.update_fields_on_open = True
document.save("with-toc.docx")

levels is the heading range to include. The document opens showing "Update this document?" and the contents appear once Word rebuilds them.

Cross-references

A cross-reference points at a bookmark, so bookmark the target first:

from docx import Document, fields

document = Document()

intro = document.add_heading("Introduction", level=1)
intro.add_bookmark("intro")

body = document.add_paragraph("See ")
body.add_field(fields.cross_reference("intro"))
body.add_run(" on page ")
body.add_field(fields.page_reference("intro"))

cross_reference() writes the bookmark's text and page_reference() the page number it falls on. See Bookmarks for the ways to create one.

Captions

Document.add_caption() writes the whole thing — the label, the self-renumbering SEQ field, and the bookmark a cross-reference needs:

document.add_picture("architecture.png")
caption = document.add_caption("Figure", "the architecture")

That gives a paragraph in the "Caption" style that Word renders as "Figure 1 the architecture", where the number is a field it recomputes whenever figures are inserted or removed. Read back before Word has computed it, the number is simply missing: caption.text is "Figure the architecture".

caption.paragraph        # -> the Paragraph, for any further formatting
caption.label            # -> "Figure"
caption.text             # -> the text as the document currently reads it
caption.number           # -> the number Word last computed, or None
caption.bookmark_name    # -> "_Ref000000001"

number is None for a caption this library has just written: a SEQ field's result is cached in the document, so it reads back only after a round trip through Word.

Referring to it later is one call:

document.add_paragraph("See ").add_field(
    fields.cross_reference(caption.bookmark_name)
)

The bookmark name is the part that matters. Word's cross-reference dialogue offers only targets whose bookmark name follows its own _Ref convention, so a caption bookmarked with an arbitrary name works but is invisible in the UI — the user cannot reference it. add_caption() generates names in that shape. (Word derives its own from a timestamp; a counter is used here instead, because a timestamp would make the same document generate different bytes on each run.) That shape also means the bookmark does not appear in Document.bookmarks, which leaves out the ones Word maintains for itself; Bookmarks.iter_all() reaches it.

Four keyword arguments adjust the result:

document.add_caption("Table", "Revenue by region", separator=": ")
document.add_caption("Figure", "A cat", restart_at_heading_level=1)  # -> "Figure 3-2"
document.add_caption("Figure", "A cat", before=document.paragraphs[0])
document.add_caption("Figure", "A cat", style=None)   # -> no style applied

separator is what goes between the number and the text. restart_at_heading_level starts the numbering over at each chapter, which is the "Figure 3-2" convention. _Cell.add_caption() does the same inside a table cell.

Each label is its own sequence, so figures, tables and equations number independently.

Assembling one by hand

SEQ is the counter underneath, and it is still there if you want to build a caption yourself:

caption = document.add_paragraph("Figure ", style="Caption")
caption.add_field(fields.sequence("Figure"))
caption.add_run(": the architecture")
caption.add_field(fields.sequence("Figure", restart_at_heading_level=1))

This is what add_caption() does, minus the bookmark.

Reading the fields in a document

Document.fields returns every field in the body, and Paragraph.fields the fields of one paragraph:

for field in document.fields:
    print(field.type, field.instruction, repr(field.result_text))

A field's cached result counts towards Paragraph.text, so the page number a reader saw is part of the text you read back.

Complex fields nest — a TOC result is full of PAGEREF fields — and each inner field is a field in its own right.

Getting Word to fill them in

Two things ask Word to compute results, and they are not the same:

document.settings.update_fields_on_open = True

Settings.update_fields_on_open sets a document-wide flag; Word prompts on open and refreshes every field. This is what a generated table of contents needs.

Individual fields are written with w:dirty set, which asks Word to refresh that field alone. Pass dirty=False to add_field() to suppress it.

To keep the field from showing as blank before the first refresh, supply cached result text of your own. That requires simple=True, because only a w:fldSimple stores its result as plain content — a complex field's result is whatever sits between its separate and end field characters, which Word writes when it computes it:

paragraph.add_field(fields.page_number(), simple=True, result="1")