Skip to content

docx_plus.fields.simple

Insert OOXML complex fields (PAGE / NUMPAGES / SECTIONPAGES, DATE / CREATEDATE, plus a generic passthrough for everything else). Each function emits the canonical 5-run sequence (begin / instrText / separate / result-text / end) with xml:space="preserve" on the instruction and the cached result so Word does not collapse field-syntax whitespace.

add_style_reference (v0.5) emits STYLEREF, the one cross-reference that needs no bookmark: it resolves to the text of the nearest paragraph carrying a given style, re-evaluated per page. That is what makes a running header show the current chapter — verified against Word 2016, where the same field renders Chapter: Architecture on page 1 and Chapter: Operations on page 2.

Note its style argument takes the style name as Word shows it ("Heading 1", with the space), not the w:styleId — unlike most of this library, because that is what the field instruction accepts. An int is an outline level instead.

Architecture walkthrough: Fields and protection.

docx_plus.fields.simple

Insert OOXML complex fields (PAGE, DATE, generic) into paragraphs.

Word fields use the complex field syntax: a sequence of runs containing w:fldChar markers (begin/separate/end) bracketing the field instruction text (w:instrText) and the result text (w:t). This module emits that five-run sequence and appends it to a paragraph.

Word recalculates field results on open only if w:updateFields is set in settings.xml — see :func:docx_plus.fields.update.mark_fields_dirty. Initial text supplied here is what Word shows before it recalculates, so the value is meaningful for offline viewers (e.g. "1" for a PAGE field).

This module imports only from docx_plus.core (SPEC §9.1).

PageFieldName module-attribute

PageFieldName = Literal['PAGE', 'NUMPAGES', 'SECTIONPAGES']

StyleRefNumber module-attribute

StyleRefNumber = Literal['plain', 'relative', 'full']

add_page_number_field

add_page_number_field(
    paragraph: Paragraph,
    *,
    field: PageFieldName = "PAGE",
    format: str | None = None,
) -> etree._Element

Append a page-number complex field to paragraph.

Parameters:

Name Type Description Default
paragraph Paragraph

A python-docx :class:~docx.text.paragraph.Paragraph. The field runs are appended after the paragraph's existing runs.

required
field PageFieldName

Which page-number variant. "PAGE" (current page), "NUMPAGES" (total pages), or "SECTIONPAGES" (pages in the current section).

'PAGE'
format str | None

Optional field switches appended to the instruction. Example: r"\* ARABIC" forces Arabic numerals, r"\* ROMAN" Roman. See ECMA-376 17.16 for the switch syntax.

None

Returns:

Type Description
_Element

The begin w:r run that marks the start of the field.

Example

from docx import Document from docx_plus.fields import add_page_number_field doc = Document() p = doc.add_paragraph("Page ") _ = add_page_number_field(p)

Source code in docx_plus/fields/simple.py
def add_page_number_field(
    paragraph: Paragraph,
    *,
    field: PageFieldName = "PAGE",
    format: str | None = None,
) -> etree._Element:
    r"""Append a page-number complex field to ``paragraph``.

    Args:
        paragraph: A python-docx :class:`~docx.text.paragraph.Paragraph`. The
            field runs are appended after the paragraph's existing runs.
        field: Which page-number variant. ``"PAGE"`` (current page),
            ``"NUMPAGES"`` (total pages), or ``"SECTIONPAGES"`` (pages in the
            current section).
        format: Optional field switches appended to the instruction. Example:
            ``r"\* ARABIC"`` forces Arabic numerals, ``r"\* ROMAN"`` Roman.
            See ECMA-376 17.16 for the switch syntax.

    Returns:
        The begin ``w:r`` run that marks the start of the field.

    Example:
        >>> from docx import Document
        >>> from docx_plus.fields import add_page_number_field
        >>> doc = Document()
        >>> p = doc.add_paragraph("Page ")
        >>> _ = add_page_number_field(p)
    """
    if format is None or not format.strip():
        instruction = f" {field} "
    else:
        instruction = f" {field} {format.strip()} "
    return build_complex_field(paragraph._p, instruction, "1")

add_date_field

add_date_field(
    paragraph: Paragraph,
    *,
    format: str = "MMMM d, yyyy",
    auto_update: bool = True,
) -> etree._Element

Append a date complex field to paragraph.

Parameters:

Name Type Description Default
paragraph Paragraph

A python-docx :class:~docx.text.paragraph.Paragraph.

required
format str

A Word date-format string. Common values: "MMMM d, yyyy" (default, e.g. May 19, 2026), "M/d/yyyy" (numeric short), "dddd, MMMM d, yyyy" (long with weekday).

'MMMM d, yyyy'
auto_update bool

True (default) emits a DATE field that Word recalculates on every open. False emits a CREATEDATE field that freezes the document's creation date.

True

Returns:

Type Description
_Element

The begin w:r run that marks the start of the field.

Example

from docx import Document from docx_plus.fields import add_date_field doc = Document() p = doc.add_paragraph("Today: ") _ = add_date_field(p, format="M/d/yyyy")

Source code in docx_plus/fields/simple.py
def add_date_field(
    paragraph: Paragraph,
    *,
    format: str = "MMMM d, yyyy",
    auto_update: bool = True,
) -> etree._Element:
    """Append a date complex field to ``paragraph``.

    Args:
        paragraph: A python-docx :class:`~docx.text.paragraph.Paragraph`.
        format: A Word date-format string. Common values:
            ``"MMMM d, yyyy"`` (default, e.g. *May 19, 2026*),
            ``"M/d/yyyy"`` (numeric short), ``"dddd, MMMM d, yyyy"`` (long
            with weekday).
        auto_update: ``True`` (default) emits a ``DATE`` field that Word
            recalculates on every open. ``False`` emits a ``CREATEDATE`` field
            that freezes the document's creation date.

    Returns:
        The begin ``w:r`` run that marks the start of the field.

    Example:
        >>> from docx import Document
        >>> from docx_plus.fields import add_date_field
        >>> doc = Document()
        >>> p = doc.add_paragraph("Today: ")
        >>> _ = add_date_field(p, format="M/d/yyyy")
    """
    keyword = "DATE" if auto_update else "CREATEDATE"
    instruction = f' {keyword} \\@ "{format}" '
    return build_complex_field(paragraph._p, instruction, "")

add_style_reference

add_style_reference(
    paragraph: Paragraph,
    *,
    style: str | int,
    search_from_bottom: bool = False,
    number: StyleRefNumber | None = None,
    position: bool = False,
    suppress_non_delimiters: bool = False,
    preserve_formatting: bool = True,
) -> etree._Element

Append a STYLEREF complex field to paragraph.

STYLEREF resolves to the text of the nearest paragraph carrying a given style — the field behind a running header that shows the current chapter title, and the one cross-reference kind that needs no bookmark at all. Word re-resolves it per page, so the same field in a header renders differently on every page.

Parameters:

Name Type Description Default
paragraph Paragraph

A python-docx :class:~docx.text.paragraph.Paragraph. Usually one belonging to a header — section.header.paragraphs[0].

required
style str | int

The style to search for. A string is the style's name as Word shows it ("Heading 1", with the space) — not the w:styleId, unlike most of this library, because that is what the field instruction takes. An int is an outline level, 1 to 9, which Word accepts as a shorthand for the corresponding built-in heading.

required
search_from_bottom bool

Append \l, so Word takes the last matching paragraph on the page rather than the first. This is what you want for a "to" half of a dictionary-style header range; the default first-match suits a chapter title.

False
number StyleRefNumber | None

Resolve to the matched paragraph's number rather than its text, with the given amount of context — see :data:StyleRefNumber. Only meaningful when the matched paragraphs are numbered.

None
position bool

Append \p, so the field resolves to "above" or "below" relative to the field itself.

False
suppress_non_delimiters bool

Append \t, dropping the matched paragraph's non-delimiter text and keeping only its numbering.

False
preserve_formatting bool

Append \* MERGEFORMAT (default True, matching what Word's UI emits for this field) so character formatting applied to the result survives recalculation.

True

Returns:

Type Description
_Element

The begin w:r run that marks the start of the field.

Raises:

Type Description
ValueError

If style is an empty string, contains a double-quote (which would terminate the quoted argument), is an int outside 1–9, or if number is not a recognised context.

Example

from docx import Document from docx_plus.fields import add_style_reference, mark_fields_dirty doc = Document() header = doc.sections[0].header.paragraphs[0] _ = add_style_reference(header, style="Heading 1") mark_fields_dirty(doc)

Notes

Like every field, this renders blank until Word recalculates. Pair it with :func:docx_plus.fields.mark_fields_dirty.

Source code in docx_plus/fields/simple.py
def add_style_reference(
    paragraph: Paragraph,
    *,
    style: str | int,
    search_from_bottom: bool = False,
    number: StyleRefNumber | None = None,
    position: bool = False,
    suppress_non_delimiters: bool = False,
    preserve_formatting: bool = True,
) -> etree._Element:
    r"""Append a ``STYLEREF`` complex field to ``paragraph``.

    ``STYLEREF`` resolves to the text of the nearest paragraph carrying a
    given style — the field behind a running header that shows the
    current chapter title, and the one cross-reference kind that needs no
    bookmark at all. Word re-resolves it per page, so the same field in a
    header renders differently on every page.

    Args:
        paragraph: A python-docx :class:`~docx.text.paragraph.Paragraph`.
            Usually one belonging to a header — ``section.header.paragraphs[0]``.
        style: The style to search for. A **string** is the style's *name*
            as Word shows it (``"Heading 1"``, with the space) — not the
            ``w:styleId``, unlike most of this library, because that is
            what the field instruction takes. An **int** is an outline
            level, ``1`` to ``9``, which Word accepts as a shorthand for
            the corresponding built-in heading.
        search_from_bottom: Append ``\l``, so Word takes the *last*
            matching paragraph on the page rather than the first. This is
            what you want for a "to" half of a dictionary-style header
            range; the default first-match suits a chapter title.
        number: Resolve to the matched paragraph's *number* rather than
            its text, with the given amount of context — see
            :data:`StyleRefNumber`. Only meaningful when the matched
            paragraphs are numbered.
        position: Append ``\p``, so the field resolves to ``"above"`` or
            ``"below"`` relative to the field itself.
        suppress_non_delimiters: Append ``\t``, dropping the matched
            paragraph's non-delimiter text and keeping only its numbering.
        preserve_formatting: Append ``\* MERGEFORMAT`` (default ``True``,
            matching what Word's UI emits for this field) so character
            formatting applied to the result survives recalculation.

    Returns:
        The begin ``w:r`` run that marks the start of the field.

    Raises:
        ValueError: If ``style`` is an empty string, contains a
            double-quote (which would terminate the quoted argument), is
            an int outside 1–9, or if ``number`` is not a recognised
            context.

    Example:
        >>> from docx import Document
        >>> from docx_plus.fields import add_style_reference, mark_fields_dirty
        >>> doc = Document()
        >>> header = doc.sections[0].header.paragraphs[0]
        >>> _ = add_style_reference(header, style="Heading 1")
        >>> mark_fields_dirty(doc)

    Notes:
        Like every field, this renders blank until Word recalculates.
        Pair it with :func:`docx_plus.fields.mark_fields_dirty`.
    """
    argument = _style_reference_argument(style)

    if number is not None and number not in _STYLEREF_NUMBER_SWITCHES:
        raise ValueError(
            f"number must be one of {sorted(_STYLEREF_NUMBER_SWITCHES)}; got {number!r}"
        )

    switches: list[str] = []
    if number is not None:
        switches.append(f"\\{_STYLEREF_NUMBER_SWITCHES[number]}")
    if search_from_bottom:
        switches.append("\\l")
    if position:
        switches.append("\\p")
    if suppress_non_delimiters:
        switches.append("\\t")
    if preserve_formatting:
        switches.append("\\* MERGEFORMAT")

    tail = f" {' '.join(switches)}" if switches else ""
    return build_complex_field(paragraph._p, f" STYLEREF {argument}{tail} ", "")

add_field

add_field(
    paragraph: Paragraph, *, instruction: str, initial_text: str = ""
) -> etree._Element

Append a generic complex field to paragraph.

Use this for fields without a dedicated helper (TOC, REF, HYPERLINK, MERGEFIELD, etc.). The instruction is wrapped in leading/trailing spaces if you don't supply them, since Word's field parser requires them.

Parameters:

Name Type Description Default
paragraph Paragraph

A python-docx :class:~docx.text.paragraph.Paragraph.

required
instruction str

The raw field instruction text without the surrounding { } braces Word shows in its UI. Example: 'REF Bookmark1' or 'TOC \o "1-3" \h'.

required
initial_text str

Optional placeholder shown before Word recalculates.

''

Returns:

Type Description
_Element

The begin w:r run that marks the start of the field.

Raises:

Type Description
ValueError

If instruction is empty or whitespace-only — Word renders the field as a silent blank, which is almost never intended (see issues.md M1).

Example

from docx import Document from docx_plus.fields import add_field doc = Document() p = doc.add_paragraph() _ = add_field(p, instruction='TOC \o "1-3" \h', initial_text="(TOC)")

Source code in docx_plus/fields/simple.py
def add_field(
    paragraph: Paragraph,
    *,
    instruction: str,
    initial_text: str = "",
) -> etree._Element:
    r"""Append a generic complex field to ``paragraph``.

    Use this for fields without a dedicated helper (``TOC``, ``REF``,
    ``HYPERLINK``, ``MERGEFIELD``, etc.). The ``instruction`` is wrapped in
    leading/trailing spaces if you don't supply them, since Word's field
    parser requires them.

    Args:
        paragraph: A python-docx :class:`~docx.text.paragraph.Paragraph`.
        instruction: The raw field instruction text without the surrounding
            ``{ }`` braces Word shows in its UI. Example: ``'REF Bookmark1'``
            or ``'TOC \o "1-3" \h'``.
        initial_text: Optional placeholder shown before Word recalculates.

    Returns:
        The begin ``w:r`` run that marks the start of the field.

    Raises:
        ValueError: If ``instruction`` is empty or whitespace-only —
            Word renders the field as a silent blank, which is almost
            never intended (see issues.md M1).

    Example:
        >>> from docx import Document
        >>> from docx_plus.fields import add_field
        >>> doc = Document()
        >>> p = doc.add_paragraph()
        >>> _ = add_field(p, instruction='TOC \\o "1-3" \\h', initial_text="(TOC)")
    """
    stripped = instruction.strip()
    if not stripped:
        raise ValueError(f"add_field requires a non-empty instruction; got {instruction!r}")
    wrapped = f" {stripped} "
    return build_complex_field(paragraph._p, wrapped, initial_text)