Make a Word document (.docx)
Activated Cloud✓ Officialactivated/make-word-document
Free · MIT
About
Builds a clean Word file (.docx) with python-docx on your own computer: real heading and list styles, tables, images, page numbers and comments, or fills and edits an existing template, then checks the file opens and looks right. Use when the owner wants a Word document, a .docx to edit or send, or a template filled in. Not for deciding what the document says (see write-structured-report) or for a fixed-layout PDF (see make-pdf-document).
Documentation
Make a Word document (.docx)
You build Word files with Python's python-docx library, run through execute_code or a script you start with terminal. The standard: the file opens cleanly in Word, LibreOffice and Google Docs, uses real styles (so the navigation pane, table of contents and restyling work), and looks finished when you render a page and look at it.
When to use
- "Can you send me that as a Word doc?"
- "Fill in our proposal template with these details."
- "Turn these notes into a formatted report I can edit."
- "Add comments to this contract where the dates look wrong."
What you need
- The content, already structured (see
write-structured-reportfor how to write it). - The owner's template (.docx or .dotx) if there is one. Using it beats any styling you invent.
- Page size: A4 for most of the world, US Letter for the US and Canada. Ask or check
memoryif unsure. - Your work space path from your instructions, normally
~/Desktop/<your name> - Work space. Make one folder per job inside it.
Set up once
Your computer runs Debian Linux with Python 3. Check for the library and install it only if missing, with terminal:
python3 -c "import docx" 2>/dev/null || python3 -m pip install --user --break-system-packages python-docx
Debian marks its Python as "externally managed", so plain pip install --user stops with an externally-managed-environment error. Adding --break-system-packages together with --user installs into your home folder only (~/.local), which survives restarts and is what execute_code imports from. The import name is docx; the package name is python-docx.
Method
Plan the styles, not the formatting. Decide font, sizes, margins and colours once and set them on the styles (
Normal,Title,Heading 1-3). Never style paragraph by paragraph.Build with
execute_code. It runs in a temporary folder, so always use absolute paths. Tested starting point:
from pathlib import Path
from docx import Document
from docx.shared import Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
JOB = Path("/home/user/Desktop/Ada - Work space/supplier-review") # your work space + this job
JOB.mkdir(parents=True, exist_ok=True)
OUT = JOB / "2026-10-05_supplier-review_v01.docx"
doc = Document() # or Document(JOB / "inputs/owner-template.docx")
for s in doc.sections:
s.page_height, s.page_width = Cm(29.7), Cm(21.0) # A4. US Letter: Inches(11), Inches(8.5)
s.left_margin = s.right_margin = Cm(2.2)
s.top_margin = s.bottom_margin = Cm(2.0)
normal = doc.styles["Normal"]
normal.font.name, normal.font.size = "Arial", Pt(11)
normal.paragraph_format.space_after = Pt(6)
for name, size in (("Title", 24), ("Heading 1", 16), ("Heading 2", 13)):
st = doc.styles[name]
st.font.name, st.font.size, st.font.color.rgb = "Arial", Pt(size), RGBColor(0x1F, 0x2A, 0x44)
def add_field(paragraph, code): # PAGE, NUMPAGES, TOC ... filled in by Word
def put(el): paragraph.add_run()._r.append(el)
b = OxmlElement("w:fldChar"); b.set(qn("w:fldCharType"), "begin"); put(b)
t = OxmlElement("w:instrText"); t.set(qn("xml:space"), "preserve"); t.text = f" {code} "; put(t)
s_ = OxmlElement("w:fldChar"); s_.set(qn("w:fldCharType"), "separate"); put(s_)
paragraph.add_run("1")
e = OxmlElement("w:fldChar"); e.set(qn("w:fldCharType"), "end"); put(e)
foot = doc.sections[0].footer.paragraphs[0]
foot.alignment = WD_ALIGN_PARAGRAPH.RIGHT
foot.add_run("Page "); add_field(foot, "PAGE"); foot.add_run(" of "); add_field(foot, "NUMPAGES")
doc.add_paragraph("Supplier review: Q3 2026", style="Title")
doc.add_heading("Summary", level=1)
doc.add_paragraph("Move packaging to Northline from January: it saves about 14% a year.")
for line in ("Saving: about 14% of annual packaging spend.", "Risk: lead time 9 days, not 5."):
doc.add_paragraph(line, style="List Bullet")
rows = [("Supplier", "Unit cost", "Lead time"), ("Acme", "£0.42", "5 days"), ("Northline", "£0.36", "9 days")]
table = doc.add_table(rows=len(rows), cols=len(rows[0]))
table.style = "Table Grid"
for r, values in enumerate(rows):
for c, value in enumerate(values):
cell = table.cell(r, c)
cell.text = value
if c > 0:
cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.RIGHT # numbers right-aligned
if r == 0:
cell.paragraphs[0].runs[0].bold = True
doc.add_paragraph("Source: supplier invoices, Jul to Sep 2026.")
doc.core_properties.title, doc.core_properties.author = "Supplier review: Q3 2026", "Ada"
doc.save(OUT)
print(OUT)
More recipes (repeat table header rows, cell shading, landscape pages, images, hyperlinks, table of contents, comments, filling templates, reading in order) are in references/docx-recipes.md.
Use the real building blocks.
- Headings:
doc.add_heading(text, level=1..3). Never fake a heading with bold Normal text. - Lists: styles
List Bullet,List Bullet 2(nested),List Number. Never type "•" or "1." by hand. - Line break inside a paragraph:
run.add_break(). A new paragraph: a newadd_paragraph. Never use empty paragraphs for spacing; setspace_before/space_afteron the style. - Page break before a major section:
doc.add_page_break()orparagraph_format.page_break_before = True. - Images:
doc.add_picture(path, width=Cm(16)), never wider than the text width (page width minus margins).
- Headings:
Working from a template or an existing file. Open it, list its styles (
[s.name for s in doc.styles]) and use those names; custom templates often rename headings. Save to a new file name (_v02), never over the owner's original. Placeholders like{{client_name}}are often split across several runs, so replace at paragraph level with the helper inreferences/docx-recipes.md, and remember headers, footers and table cells.Check the file (next section), then tell the owner where it is.
Checking the output
- Structure (always): reopen with
Document(OUT)and print every heading with its style, the table count and the number of empty paragraphs. Headings in the wrong order or empty paragraphs used as spacers show up here. - Look (for anything the owner will send or print): convert to PDF and render a page to an image, then look at it.
command -v soffice || sudo apt-get install -y --no-install-recommends libreoffice-writer-nogui
python3 -c "import pypdfium2" 2>/dev/null || python3 -m pip install --user --break-system-packages pypdfium2
cd "/home/user/Desktop/Ada - Work space/supplier-review" && mkdir -p check
soffice -env:UserInstallation=file:///tmp/lo-check --headless --convert-to pdf --outdir check 2026-10-05_supplier-review_v01.docx
python3 -c "import pypdfium2 as p; d=p.PdfDocument('check/2026-10-05_supplier-review_v01.pdf'); print(len(d), 'pages'); [d[i].render(scale=1.4).to_pil().save(f'check/page{i+1}.png') for i in range(min(len(d),3))]"
Then call vision_analyze on check/page1.png (full path) and ask: are headings, tables and page numbers right, does anything overflow or look cramped? Apt installs live outside your home folder, so after your computer is rebuilt you may need to install LibreOffice again. Delete the check folder when you are done.
- LibreOffice substitutes fonts it lacks (Calibri and Aptos become look-alikes), so line breaks can differ slightly from Word. Judge layout, not exact line endings.
Output
- The .docx in the job folder, named
YYYY-MM-DD_topic_type_v01.docx. - One message: what it is, full path, page count, anything the owner must do (for example "press F9 in Word to fill the table of contents").
Checks before you finish
- Opens without error when re-read; headings use Heading styles; lists use list styles.
- No empty paragraphs used for spacing; no table or image wider than the text area.
- Page numbers present on documents over two pages; title, author and date filled in.
- Every number matches its source; names spelled right.
- The owner's original file is untouched; your output has a new version number.
Pitfalls
- "\n" inside
add_paragraphgives line breaks within one paragraph, not separate paragraphs, so list and heading styles break. Add one paragraph per item. - Numbering that carries on. Every
List Numberparagraph in a file shares one counter, so a second numbered list continues from the first. Keep one numbered list per document, or use bullets for the second. - KeyError on a style name with an owner template: the template does not have that style. Print the style names and pick the template's own.
- Tracked changes are not supported by python-docx. Do not pretend to track changes; add comments (
doc.add_comment) or send a list of edits, and tell the owner. - Table of contents fields are filled in by Word when the owner updates fields, not by python-docx. Say so, or skip the contents list for documents under about 10 pages.
- Overwriting the owner's file or saving in
/tmp. Always a new versioned file in your work space.
See also: write-structured-report (what to write), make-pdf-document (fixed layout, or a PDF copy), make-clear-charts (images to insert).
Versions
Listed from the source repository.
Reviews
No reviews yet. Be the first.
