Skip to main content

docx_plus

OOXML-level extensions for python-docx.

PyPI Python versions CI Docs License: MIT Typed Ruff

Documentation · API index · Architecture · Changelog · Roadmap


python-docx is an excellent library that stops at a well-defined boundary. Past that boundary — the style cascade, content controls, anchored comments, tracked changes, custom numbering, table borders — the usual answer is a StackOverflow snippet that reaches into element._p and builds raw lxml by hand. Everyone writing serious document automation ends up with a private, half-tested pile of that code.

docx_plus is that pile, done properly: typed, tested against documents Word itself authored, and schema-strict about where elements are allowed to go. It composes with python-docx rather than replacing it — you keep your Document object and reach for docx_plus only where you need to.

from docx import Document
from docx_plus.styles import resolve_effective_formatting

doc = Document("report.docx")

# "Why is this heading 13pt and blue?" — a question python-docx can't answer,
# because the value is inherited, not set on the paragraph at all.
resolved = resolve_effective_formatting(doc.paragraphs[0], include_provenance=True)

print(resolved.font_size)                # 13.0
print(resolved.provenance["font_size"])  # FormattingSource(layer='paragraphStyle',
                                         #                  style_id='Heading2',
                                         #                  chain_depth=0, ...)

Install

pip install docx-plus
uv add docx-plus

Requires Python 3.10+. The only dependencies are python-docx and lxml.

What it does

Capability Module
Styles Resolve the effective formatting of any paragraph / run / cell through the full eight-layer cascade, with per-field provenance. Create, modify, and remap styles; materialise any of 107 latent Word built-ins. styles/
Content controls Text, dropdown, date, and checkbox controls via FormBuilder; read and write their values; round-trip through save / reopen. controls/
Comments Anchored comments with the body-side range markers python-docx omits — so Word's "show in document" actually works. Plus threading (reply / resolve / reopen), durable ids, and author presence. comments/
Tracked changes Mark runs as insertions or deletions, read every revision with author / timestamp / text, accept or reject them, toggle track-changes mode. revisions/
Fields PAGE / NUMPAGES / DATE and generic complex fields; mark fields dirty so Word recalculates on open. fields/
Tables Table / row / cell borders and shading, cell merging and unmerging, w:hMerge normalization, direct-formatting reads. tables/
Numbering Custom bullet and multi-level numbered list definitions, applied and restarted per paragraph. numbering/
Layout Multi-column sections, mid-document section breaks, distinct even/odd headers, line numbering, page borders. layout/
Bookmarks Paired body markers plus REF / PAGEREF cross-references. bookmarks/
Notes Footnotes and endnotes over the separate footnotes.xml / endnotes.xml parts; insert and edit in place. notes/
Publishing Table of Contents, figure / table captions via SEQ, Table of Figures. publishing/
Protection Form-fill, read-only, comments-only, or tracked-changes enforcement at the document level. protection/
Lint Audit a document for direct formatting fighting the styles, skipped outline levels, hand-typed lists, and whitespace used as layout — then describe the repair as an ordered, serializable plan. Read-only. lint/
CLI docx-plus inspect / restyle / controls / comments / lint / plan / skill — the library from a shell. cli/

Quickstart

A few of the most-used surfaces. The documentation covers all of them, and every module has runnable examples under docx_plus/examples/.

Styles: define once, apply everywhere

from docx import Document
from docx_plus.styles import apply_style, create_style, ensure_style

doc = Document()
create_style(
    doc, "BrandHeading",
    style_type="paragraph",
    based_on="Heading1",
    font_name="Inter",
    font_size=18.0,
    color_rgb="2F5496",
    bold=True,
    spacing_after=240,
)
apply_style(doc.add_paragraph("Hello, world"), "BrandHeading")
doc.save("out.docx")

This is the Word-native workflow: define a style, apply it. Change the style later and every paragraph using it follows — unlike direct formatting, which you have to remember to update everywhere.

Word's built-ins (Heading1Heading9, Title, Quote, TOC1TOC9, FootnoteText, …) are latent: defined by Word's defaults but absent from styles.xml until used. ensure_style materialises them idempotently, with defaults extracted from real Word-saved samples rather than guessed:

ensure_style(doc, "Heading1")   # materialises if absent
ensure_style(doc, "Heading1")   # ...no-op the second time
ensure_style(doc, "TOC2")       # 107 built-ins known

For documents authored elsewhere, where a style may be named "Heading 1" with a space, ensure_style(doc, "Heading1", match_existing=True) finds the existing definition via case- and space-insensitive matching — or use remap_styles for document-wide normalisation.

Forms: build a fillable document

from docx_plus.controls import FormBuilder

fb = FormBuilder()  # or FormBuilder("template.docx")
fb.doc.add_heading("New employee form", level=1)

p = fb.doc.add_paragraph("Full name: ")
fb.add_text_control(p, tag="full_name", placeholder="Type your name")

p = fb.doc.add_paragraph("Department: ")
fb.add_dropdown(p, tag="dept", items=["Engineering", "Design", "Ops"])

p = fb.doc.add_paragraph("Start date: ")
fb.add_date_picker(p, tag="start_date", date_format="M/d/yyyy")

fb.save("form.docx")

Read and update an existing form's values:

from docx import Document
from docx_plus.controls import read_controls, set_control_value

doc = Document("form.docx")
set_control_value(doc, "full_name", "Ada Lovelace")
doc.save("filled.docx")

values = read_controls(Document("filled.docx"))
print(values["full_name"].value)   # 'Ada Lovelace'

Comments: anchored to the text they're about

from docx import Document
from docx_plus.comments import add_comment, read_comments, reply_to_comment

doc = Document()
p = doc.add_paragraph()
p.add_run("Project Apollo ")
target = p.add_run("ships next quarter")

c = add_comment(target, "Optimistic — let's see what QA says.", author="Alice")
reply_to_comment(doc, c.comment_id, "Agreed, moving to Q3.", author="Bob")

for comment in read_comments(doc):
    print(f"{comment.author}: {comment.text!r} on {comment.anchored_text!r}")

add_comment accepts a Run, a Paragraph, or a (start_run, end_run) tuple for ranges. Unlike python-docx's Comments.add_comment — which writes only the part-side body — docx_plus writes the three body-side anchors, so the comment is attached to a real span of text.

Tracked changes: propose edits, then accept or reject

from docx import Document
from docx_plus.revisions import accept_revision, mark_insertion, read_revisions

doc = Document()
p = doc.add_paragraph("The report is ")
mark_insertion(p.add_run("nearly "), author="Alice")
p.add_run("complete.")

for rev in read_revisions(doc):
    print(rev.revision_type, rev.author, rev.text)   # 'insertion' 'Alice' 'nearly '

accept_revision(doc, rev.revision_id)   # or reject_revision / accept_all_revisions

Publishing: TOC, captions, Table of Figures

from docx import Document
from docx_plus.fields import mark_fields_dirty
from docx_plus.publishing import add_caption, add_table_of_figures, add_toc

doc = Document()
doc.add_heading("Contents", level=1)
add_toc(doc.add_paragraph(), levels=(1, 2))

doc.add_heading("Architecture", level=1)
cap = doc.add_paragraph()
add_caption(cap, "Figure ", caption_type="Figure")
cap.add_run(": System overview.")

doc.add_heading("List of Figures", level=1)
add_table_of_figures(doc.add_paragraph(), caption_type="Figure")

mark_fields_dirty(doc)   # Word populates TOC / SEQ / ToF on open
doc.save("paper.docx")

Command line

docx-plus installs a console command (also python -m docx_plus.cli) for inspecting and editing documents from a shell:

$ docx-plus inspect report.docx --provenance        # effective formatting per paragraph
$ docx-plus restyle draft.docx --target Heading1 -o clean.docx
$ docx-plus controls list form.docx --json          # every content control
$ docx-plus controls set form.docx --tag name --value "Ada Lovelace" -o filled.docx
$ docx-plus comments list draft.docx --unresolved   # open comment threads
$ docx-plus lint report.docx                        # formatting defects
$ docx-plus plan report.docx                        # what repairing them would change
$ docx-plus skill install                           # drop the agent skill into .claude/skills/

Read commands take --json. Mutating commands require -o/--output (or an explicit --in-place) so the source is never overwritten by accident. lint and plan exit 1 when they found something, so either drops into a CI step directly. Full reference: CLI docs.

For AI coding agents

docx_plus ships an agent skill inside the package — a structured guide to the API that Claude Code (or any agent that reads skill files) can load instead of guessing at signatures. pip install docx-plus is enough to get it:

$ docx-plus skill install      # copies it into ./.claude/skills/

See docx_plus/skill/SKILL.md and the skills overview.

Documentation

Full docs are published at https://thomas-villani.github.io/docx-plus/, built with MkDocs and mkdocstrings.

  • API index — hand-curated index of every public symbol, linked to the generated reference.
  • Architecture — module layout, the cascade algorithm, schema-strict insertion, the error hierarchy, and the invariants the library maintains. Read this if you want to know why the OOXML looks the way it does.
  • CLI reference.
  • Test gaps — an honest accounting of where the suite has real holes.

Project status

v0.6.0, released 2026-07-31 — beta, and shipping. 2,043 tests, 96% coverage, mypy --strict clean with zero ignores. CI runs Python 3.10–3.13 on Linux plus a Windows job, and a lower-bound dependency job pinned to python-docx==1.0.0 / lxml==4.9.0.

The API is stable in practice but pre-1.0: breaking changes are possible on minor versions and will be called out in CHANGELOG.md.

ROADMAP.md is the live record of what is shipped, backlogged, and deliberately declined. Currently on the backlog: content-control data binding to Custom XML Parts, bibliography and BIBLIOGRAPHY fields, theme writing, glossary placeholder text, and password-protected forms. If your use case needs one of these, open an issue — demand reorders the list.

Release history
  • v0.1.0 — foundation (core/), style inspection / modification / remapping, content controls, fields, and document protection.
  • v0.2.0 — comments, layout, bookmarks, notes, core/parts, plus toggle properties, in-place edit verbs, line numbering, page borders, conditional table styles, and publishing/.
  • v0.3.0 — tracked changes (revisions/) and the docx-plus CLI.
  • v0.4.0 — threaded comments over commentsExtended.xml, and docx-plus comments.
  • v0.5.0 — table formatting (tables/), custom numbering (numbering/), comment durable ids and author presence (commentsIds.xml / people.xml), and the agent skill shipping in the wheel behind docx-plus skill.
  • v0.6.0 — the document linter (lint/) with 20 rules, profiles, and plan_fixes; docx-plus lint / plan; the cascade resolver corrected against live Word (toggles, conditional table formatting, theme colours, paragraph spacing, the default paragraph style); the document-wide sweep, stop_below baselines, and read_fields.

Contributing

Contributions are welcome — see CONTRIBUTING.md for the development setup, the quality gates, and the conventions. In short:

git clone https://github.com/thomas-villani/docx-plus.git
cd docx-plus
uv sync --extra dev
uv run pre-commit install
uv run pytest

Bug reports are most useful with a minimal .docx attached, or the offending fragment of word/document.xml.

Security issues should be reported privately — see SECURITY.md.

License

MIT. Copyright (c) 2026 Tom Villani, PhD. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

docx_plus-0.6.0.tar.gz (512.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

docx_plus-0.6.0-py3-none-any.whl (346.1 kB view details)

Uploaded Python 3

File details

Details for the file docx_plus-0.6.0.tar.gz.

File metadata

  • Download URL: docx_plus-0.6.0.tar.gz
  • Upload date:
  • Size: 512.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for docx_plus-0.6.0.tar.gz
Algorithm Hash digest
SHA256 89485b2618e99a80da5041813a4273d0394ede28a7e9fab514c194817196b2a5
MD5 11811b97dc3f9fdc379449592d8320a9
BLAKE2b-256 8577cc18dd58b4393d910cfaf440eb2e74a553872e244b75fb5b0d1622c189e0

See more details on using hashes here.

Provenance

The following attestation bundles were made for docx_plus-0.6.0.tar.gz:

Publisher: release.yml on thomas-villani/docx-plus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docx_plus-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: docx_plus-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 346.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for docx_plus-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 efd5a38dab10a53c41aae3d84abb1659ed2e5204ac78396dc21dd58d97c9424d
MD5 faa05135abb860d33963675a026970ba
BLAKE2b-256 6fe71890784ecf18f6575944f9fd0f4718ec6bd879996ced20428d56d1850331

See more details on using hashes here.

Provenance

The following attestation bundles were made for docx_plus-0.6.0-py3-none-any.whl:

Publisher: release.yml on thomas-villani/docx-plus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.6.2

2 files

0.6.1

2 files

This release

0.6.0 This release

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page