Document properties¶
Word keeps three separate sets of properties, in three parts of the package. They are not interchangeable, and which one you want depends on who wrote the value.
| Part | Written by | |
|---|---|---|
| Core | docProps/core.xml |
you, and Word's File → Info panel |
| Extended | docProps/app.xml |
Word, mostly statistics |
| Custom | docProps/custom.xml |
you, under any name you like |
Core properties¶
The standard Dublin Core set — the fields Word shows under File → Info:
from docx import Document
document = Document()
core = document.core_properties
core.title = "Quarterly report"
core.author = "Ada Lovelace"
core.subject = "Finance"
core.keywords = "quarterly, finance, 2026"
core.comments = "Draft for review"
core.category = "Report"
core.content_status = "Draft"
core.language = "en-GB"
The date fields take a datetime:
import datetime as dt
core.created = dt.datetime(2026, 3, 14, 9, 0)
core.modified = dt.datetime.now()
Also available: identifier, last_modified_by, last_printed, revision (an int)
and version.
Unset string properties read as "" rather than None, and unset dates as None. The
date properties accept only a datetime on assignment — assigning None to clear one
raises ValueError.
Extended properties¶
docProps/app.xml is mostly Word's own bookkeeping — how many pages, words and
characters the document had when Word last saved it:
extended = document.extended_properties
extended.pages
extended.words
extended.characters
extended.paragraphs
extended.lines
extended.total_time # editing minutes
extended.application # e.g. "Microsoft Office Word"
extended.app_version
Warning
These statistics are whatever Word wrote when it last saved. This library does not recompute them, so a document you have edited here reports the old counts. Treat them as a record of Word's last visit, not as a live measure.
A few are yours to set, and Word displays them:
extended.company = "Analytical Engines Ltd"
extended.manager = "Charles Babbage"
extended.hyperlink_base = "https://example.com/docs/"
template names the template the document was created from.
Note
CT_Properties is an xsd:all type, so Word writes these children in an order that
does not match the order the schema lists them in. That is expected, and nothing in
the API depends on their order.
Custom properties¶
docProps/custom.xml holds properties under any name you choose, which is what makes it
useful for carrying your own metadata through a document. It behaves as a dict:
import datetime as dt
props = document.custom_properties
props["Matter number"] = 4242
props["Reviewed"] = True
props["Reviewed on"] = dt.datetime(2026, 3, 14)
props["Rate"] = 1.5
props["Client"] = "Analytical Engines Ltd"
props["Matter number"] # -> 4242
"Client" in props # -> True
len(props)
list(props) # -> the names
del props["Reviewed"]
A value may be a str, int, float, bool or datetime; those are the variant types
Word writes, and each reads back as the same Python type. Assigning anything else raises
ValueError rather than writing a file Word would refuse to open.
Names are unique and case-sensitive, and may contain spaces.
Showing one in the document¶
A DOCPROPERTY field puts a custom property's value into the text, so it updates
wherever it appears when the property changes (Fields):
from docx import fields
paragraph = document.add_paragraph("Matter: ")
paragraph.add_field(fields.doc_property("Matter number"))
The custom XML data store¶
A different thing from the properties above, and easy to confuse with them. The three
kinds of properties are flat named scalars — a string, a number, a date. The custom XML
data store holds arbitrary XML documents, each in a part of its own
(customXml/item1.xml, with an itemProps1.xml sidecar declaring its schemas).
This is where a document-generation pipeline keeps the structured data its content controls are bound to. Word's data binding points a content control at an XPath into one of these items, so the control's displayed text and the stored data stay the same thing.
part = document.add_custom_xml_part(
"<invoice xmlns='urn:example:invoice'>"
"<number>2026-014</number><total>1450.00</total>"
"</invoice>",
schema_refs=("urn:example:invoice",),
)
part.item_id # -> "{...}", the GUID Word identifies the item by
part.schema_refs # -> ("urn:example:invoice",)
part.xml # -> the XML as text
Reading them back:
for part in document.custom_xml_parts:
print(part.partname, part.item_id, part.schema_refs)
print(part.xml)
Document.custom_xml_parts is a tuple in
relationship order, empty for a document that has no data store.
add_custom_xml_part() accepts a str or
bytes, allocates the next free partname, and generates the w:itemProps sidecar with a
GUID of its own — Word requires the sidecar, and an item without one is a repair prompt.
schema_refs names the namespaces the item uses. It is optional and it is what Word's
XML mapping pane lists, so supplying it is what makes the item usable for data binding
through the UI.
Warning
The generated GUID is the one thing in this library's output that is not a function
of its input. Everything else about a saved document is deterministic — see
Reproducible output. If that matters to you, supply the item_id:
It only has to be unique within the document.
Note
A document created by this library carries no data store. The bundled template
used to ship an empty bibliography item left over from its author's Word session,
which meant custom_xml_parts reported a store the caller had never added. That is
gone as of 2.1.0.