mrkeyoor.com_
Sun 20 Sept 11:42 UTC
PyPIDataupdated 20 Sept 2026

python-docx review

python-docx 1.2.0 reads, creates, changes, and saves the Office Open XML package used by Word 2007 and later .docx files. Its public objects follow document structure: sections contain headers, footers, and body content; paragraphs contain runs and hyperlinks; cells contain paragraphs and nested tables. It edits that structure but does not run Word's page-layout engine, so it cannot promise page numbers, final line breaks, or PDF rendering. Version 1.2 adds comments anchored to a run or consecutive runs, drops Python 3.8, and adds Python 3.13 testing.

Verdict

python-docx 1.2.0 installed in 0.3 seconds and used 14 MB across 3 packages in our sandbox, then import docx worked in 0.27 seconds with 0 audit findings. Use it for editable Word structure; choose a renderer or converter when final pages and PDFs are the requirement.

We installed it

Lab card: what happened when we installed python-docxScreenshot of python-docx documentation
Install✓ · 0.3s3 packages on disk · 14 MB
Importimport docx in 0.27s · pure Python · py.typed · requires Python >=3.9
Known vulns0(pip-audit)

Answers from our run

Does python-docx install cleanly?

Yes. In a fresh container with an empty cache, pip install python-docx finished in 0.3s, leaving 3 packages and 14 MB on disk. pip-audit reported no known vulnerabilities.

What does python-docx need to run?

Python >=3.9, and nothing compiled: it is pure Python. In our run import docx succeeded in 0.27s, and the package ships py.typed for type checkers.

python-docx or docxtpl: which should you use?

docxtpl: Choose it when document owners edit a Word template and Jinja placeholders drive repeated report generation. python-docx 1.2.0 installed in 0.3 seconds and used 14 MB across 3 packages in our sandbox, then import docx worked in 0.27 seconds with 0 audit findings.

When should you not use python-docx?

The deliverable is a PDF or must break at exactly the same page and line on every machine; python-docx has no rendering engine

API stability5/5Document, Paragraph, Run, Table, Section, styles, and save retain the same roles in version 1.2. The 1.x line added ordered content iteration, hyperlink information, rendered page-break markers, and comments around those objects instead of replacing them. Python 3.8 support ended in 1.2, which is an environment break rather than an object-model rewrite. Code using private _element and _p members receives none of this stability and needs its own tests.
Docs4/5Read the Docs combines practical guides with API pages for documents, paragraphs, runs, hyperlinks, tables, sections, styles, comments, units, and package internals. It explains starting templates and file-like streams, and versioned history names the Python floor. Coverage thins where the public API ends. Footnotes, fields, tracked changes, charts, and text boxes often send readers to OOXML references or issue discussions, which keeps the score at 4.
Maintenance3/5Version 1.2.0 was released on 2025-06-16 with comment support, a Python 3.9 floor, and Python 3.13 testing. GitHub shows the unarchived repository was pushed on 2026-08-01 and reports 513 open issues and pull requests. Work continues, but the queue reflects a very broad Word format and many requested features remain outside the high-level API. Teams using private XML helpers should expect to own them for a long time.
Ecosystem4/5The recorded weekly volume is 27,505,788 downloads, and GitHub reports 5,697 stars. docxtpl builds template-driven generation on the same object model, while Mammoth and Pandoc-based packages cover extraction and conversion. The output is a standard .docx that office applications can edit. Accurate rendering and PDF generation still require Word, LibreOffice, or another external engine, so the Python package does not cover the entire document workflow.

Use it if

  • The output must remain an editable .docx that a reviewer can open and revise in Word or LibreOffice
  • A company template already defines styles, margins, headers, footers, numbering, and theme choices for generated reports
  • Code needs public access to paragraphs, runs, hyperlinks, tables, images, sections, comments, or core document properties
  • Review automation must attach a version 1.2 comment to an exact run or contiguous group of runs
Skip it if

Setup reality

We installed python-docx 1.2.0 without a cache in a fresh Python 3.12 Bookworm sandbox. Installation finished in 0.3 seconds and produced 3 packages occupying 14 MB. The distribution declares 2 direct dependencies, requires Python 3.9 or newer, is pure Python, ships py.typed, and uses the MIT license. pip-audit found 0 known vulnerabilities. The installed project name differs from its import: import docx succeeded in 0.27 seconds.

No service account or configuration file is involved. Start from a checked-in .docx template when layout and brand styles matter. Named styles must exist in that source document before code assigns them. Sections, headers, footers, page size, and theme defaults also come from the template. A blank Document uses python-docx's bundled default file rather than your organization's Word settings.

Text replacement is the common trap in version 1.2. Word may divide one visible sentence into runs because of bold text, hyperlinks, fields, or editing history. Setting paragraph.text discards that run structure and its inline formatting. Comments also anchor to run boundaries. Build important phrases as their own runs, or write a tested replacement routine that maps characters back to runs.

python-docx writes XML and embedded parts; it does not calculate pagination or convert to PDF. Features outside the public API often require _element edits against the OOXML tree. Isolate those helpers and test the saved file in Word and LibreOffice. Save can target BytesIO, but rewind the buffer before returning it over HTTP. Concurrent jobs should use separate Document and stream objects.

Patterns

Write a new editable report create-word-document

from docx import Document

doc = Document()
doc.add_heading('Incident report', level=1)
doc.add_paragraph('Service recovered at 14:32 UTC.')
doc.save('incident-report.docx')

Install python-docx, then import docx. The saved file contains editable Word structure and no fixed page rendering.

Inherit styles from a company file start-from-template

from docx import Document

doc = Document('templates/company-report.docx')
doc.add_paragraph('Executive summary', style='Heading 1')
doc.add_paragraph(summary, style='Body Text')
doc.save('report.docx')

Heading 1 and Body Text must exist in the template. Its sections, headers, footer links, and theme also carry into output.

Iterate paragraphs and tables in document order walk-body-order

from docx import Document
from docx.table import Table
from docx.text.paragraph import Paragraph

doc = Document('submission.docx')
for block in doc.iter_inner_content():
    if isinstance(block, Paragraph):
        print('paragraph', block.text)
    elif isinstance(block, Table):
        print('table rows', len(block.rows))

doc.paragraphs and doc.tables are separate lists. iter_inner_content preserves their order in the main body.

Format one phrase without flattening the paragraph format-selected-text

from docx.shared import Pt, RGBColor

paragraph = doc.add_paragraph('Status: ')
run = paragraph.add_run('OVERDUE')
run.bold = True
run.font.size = Pt(12)
run.font.color.rgb = RGBColor(0xB0, 0x00, 0x20)

Inline formatting belongs to runs. Reassigning paragraph.text would replace these runs with one plain-text run.

Append rows to a Word table build-table

table = doc.add_table(rows=1, cols=2)
table.style = 'Table Grid'
table.rows[0].cells[0].text = 'Region'
table.rows[0].cells[1].text = 'Orders'

for region, count in rows:
    cells = table.add_row().cells
    cells[0].text = region
    cells[1].text = str(count)

Setting cell.text replaces its contents with plain text. Add runs inside cell.paragraphs[0] when character styling matters.

Add a centered image with a fixed width insert-sized-image

from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Inches

doc.add_picture('chart.png', width=Inches(6))
doc.paragraphs[-1].alignment = WD_ALIGN_PARAGRAPH.CENTER

Supplying only width preserves the image's aspect ratio. With no size argument, embedded resolution metadata affects its dimensions.

Rotate one section to horizontal pages make-horizontal-section

from docx.enum.section import WD_ORIENT
from docx.shared import Inches

section = doc.sections[0]
section.orientation = WD_ORIENT.LANDSCAPE
section.page_width, section.page_height = section.page_height, section.page_width
section.left_margin = Inches(0.7)
section.right_margin = Inches(0.7)

Changing orientation alone is insufficient. Swap page width and height so the office application receives the horizontal dimensions.

Return a document as bytes save-to-memory

from io import BytesIO

buffer = BytesIO()
doc.save(buffer)
buffer.seek(0)
payload = buffer.read()

save accepts a binary file-like object. Seek back to position 0 before an HTTP response or another consumer reads it.

Anchor a review comment to a deadline comment-on-run

paragraph = doc.add_paragraph('Ship by ')
deadline = paragraph.add_run('31 August')
doc.add_comment(
    deadline,
    text='Confirm this date with operations.',
    author='Review bot',
    initials='RB',
)

Document comments were added in version 1.2. Isolate the target phrase in its own run for an exact anchor.

Read text from every body table extract-table-cells

from docx import Document

doc = Document('forms.docx')
for table in doc.tables:
    for row in table.rows:
        print([cell.text.strip() for cell in row.cells])

Nested tables live under cell.tables. Tables in headers and footers belong to those parts and do not appear in doc.tables.

Inspect section header and footer text read-headers-footers

for section in doc.sections:
    for paragraph in section.header.paragraphs:
        print(paragraph.text)
    for paragraph in section.footer.paragraphs:
        print(paragraph.text)

Several sections may link to the same header part. Track part identity when repeated output would be wrong.

Delete a paragraph through private OOXML remove-paragraph-xml

def remove_paragraph(paragraph):
    element = paragraph._element
    element.getparent().remove(element)
    paragraph._p = paragraph._element = None

for paragraph in list(doc.paragraphs):
    if paragraph.text.startswith('[REMOVE]'):
        remove_paragraph(paragraph)

Version 1.2 has no public paragraph deletion method. This private-element helper needs a saved-file regression test on upgrades.

Alternatives

PackageRegistryPick it when
docxtplPyPIChoose it when document owners edit a Word template and Jinja placeholders drive repeated report generation.
mammothPyPIChoose it to extract semantic HTML from .docx input rather than edit the Word package.
pypandocPyPIChoose it when conversion among document formats matters more than direct paragraph, run, and table access.

More data guides

numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.