Skip to main content

docx_plus

OOXML-level extensions for python-docx.

PyPI Python versions CI Docs License: MIT Typed Ruff

Documentation · Getting started · Guides · API index · 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

Four of the most-used surfaces, each with a link to its full guide. Every capability has one — see the guides index — plus 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. ensure_style materialises any of Word's 107 latent built-ins with defaults extracted from real Word-saved samples, and remap_styles reconciles documents authored elsewhere that call the same style "Heading 1".

Styles guide

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'

Forms guide — including protect_document(mode="forms") to lock everything but the controls.

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.

Comments guide

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")

That mark_fields_dirty call is not optional — every field the library writes (TOC, captions, cross-references, page numbers, dates) renders blank on disk until Word recalculates it.

Publishing guide

The rest

Tracked changes, tables, numbering, layout, bookmarks, footnotes, and the linter each have their own guide:

Tracked changes Mark insertions / deletions, read revisions, accept or reject
Tables Borders, shading, merging, legacy w:hMerge normalization
Lists and numbering Bullet and multi-level definitions, applied and restarted
Page layout Columns, mid-document section breaks, line numbers, page borders
Bookmarks Paired markers plus REF / PAGEREF cross-references
Footnotes and endnotes Insert and edit in place over the separate parts
Linting Audit an inherited document, and describe the repair

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.

  • Getting started — install, your first script, and the seven conventions that apply across every module. Start here.
  • Guides — one task-oriented page per capability: styles, forms, comments, tracked changes, tables, publishing, linting, and the rest.
  • Concepts — the cascade algorithm, schema-strict insertion, the parts model, the error hierarchy, and the invariants the library maintains. Read this if you want to know why the OOXML looks the way it does.
  • API index — hand-curated index of every public symbol, linked to the generated reference.
  • CLI reference.
  • Test gaps — an honest accounting of where the suite has real holes.

Project status

v0.6.1, released 2026-08-21 — beta, and shipping. 2,098 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.1.tar.gz (523.4 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.1-py3-none-any.whl (351.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: docx_plus-0.6.1.tar.gz
  • Upload date:
  • Size: 523.4 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.1.tar.gz
Algorithm Hash digest
SHA256 e00857d9f8a6c13cf42c515e91490fed0df6ae985abdd7fe5ce27a6b8710a003
MD5 3668dce58d5b707b43ffcdef0f5ee36a
BLAKE2b-256 85c0c3ea84859c0ed3af328cbaaa641fdf2c5a606d491bd23100b44585aeda86

See more details on using hashes here.

Provenance

The following attestation bundles were made for docx_plus-0.6.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: docx_plus-0.6.1-py3-none-any.whl
  • Upload date:
  • Size: 351.9 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 27e0726da48dbadbb493fc41e832dbedf910e0ff65cec9ffaf238ce1fef982aa
MD5 7a548f1411fec2380ac213c1f8a81ff3
BLAKE2b-256 9291f397bb66780d1f8ec959c54f423ba6d7c7363d38e7f169b2acdc706c3ebd

See more details on using hashes here.

Provenance

The following attestation bundles were made for docx_plus-0.6.1-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

This release

0.6.1 This release

2 files

0.6.0

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