Docx Skill
Create, read, edit, and template Microsoft Word .docx files with
python-docx via small CLIs. It handles text, styles, lists, tables,
images, headers/footers, {{token}} templating, tracked changes
(list/accept/reject), comments (list/add/delete), TOC and page-number
fields, and package health checks. It does not render documents itself
(PDF needs LibreOffice — see Converting to PDF) or edit legacy .doc.
When to Use
- The user asks to generate a Word document (report, letter, contract).
- You need the text, outline, styles, or embedded images of a
.docx.
- You must change an existing
.docx: replace text, edit table cells,
insert/delete paragraphs, apply styles, merge fragmented runs.
- You have a
.docx template with {{placeholders}} to fill from data.
- The document has tracked changes to review, accept, or reject.
- You need to read reviewers' comments, or add/delete comments.
- A
.docx won't open or behaves oddly and you need corruption triage.
- The document needs a table of contents or "Page X of Y" footers.
- Not for:
.doc (legacy), .odt, or WYSIWYG layout work.
Prerequisites
- Python 3.10+ with
python-docx installed:
pip install python-docx (import name is docx; lxml comes with it).
- Comments
add uses the native API on python-docx >= 1.2 and an XML
fallback on older versions — both are automatic.
- For image blocks: the image files must exist locally (PNG/JPEG).
How to Run
All helpers live in scripts/ next to this file. Run them with the
terminal tool; each supports --help and prints JSON to stdout.
python scripts/docx_create.py spec.json out.docx
python scripts/docx_read.py out.docx --text
python scripts/docx_edit.py replace out.docx --find old --replace new
python scripts/docx_template.py tpl.docx values.json filled.docx
python scripts/docx_revisions.py list out.docx
python scripts/docx_comments.py list out.docx
python scripts/docx_validate.py out.docx
Quick Reference
| Task |
Command |
| Create from JSON spec |
docx_create.py spec.json out.docx |
| Full text (body+tables+headers/footers) |
docx_read.py f.docx --text |
| Heading outline + table shapes |
docx_read.py f.docx --structure |
| Styles actually used |
docx_read.py f.docx --styles |
| Extract embedded images |
docx_read.py f.docx --images outdir/ |
| Detect tracked changes/comments |
docx_read.py f.docx --revisions |
| Find/replace (formatting kept) |
docx_edit.py replace f.docx --find A --replace B -o out.docx |
| Set a table cell |
docx_edit.py set-cell f.docx --table 0 --row 1 --col 2 --text X |
| Insert paragraph before index N |
docx_edit.py insert f.docx --index N --text X --style Normal |
| Delete paragraph N |
docx_edit.py delete f.docx --index N |
| Apply style to paragraph N |
docx_edit.py style f.docx --index N --style "Heading 1" |
| Merge equal-format adjacent runs |
docx_edit.py normalize f.docx -o out.docx |
| Insert TOC field before para N |
docx_edit.py toc f.docx --index N -o out.docx |
| "Page X of Y" footer fields |
docx_edit.py page-numbers f.docx |
Fill {{tokens}} |
docx_template.py tpl.docx values.json out.docx --strict |
| List revisions (id/author/date/text) |
docx_revisions.py list f.docx |
| Accept / reject all revisions |
docx_revisions.py accept-all f.docx -o out.docx (or reject-all) |
| Accept / reject one revision |
docx_revisions.py accept f.docx --id 3 -o out.docx |
| List comments (+anchored text) |
docx_comments.py list f.docx |
| Add comment anchored to text |
docx_comments.py add f.docx --target "phrase" --text "note" --author You |
| Delete comment by id |
docx_comments.py delete f.docx --id 0 |
| Health-check the package |
docx_validate.py f.docx (exit 1 on errors) |
Procedure
- Create. Write a JSON spec with
write_file, then run
scripts/docx_create.py. The spec supports: page (size + margins in
mm), header/footer strings, footer_page_numbers (adds a
"Page X of Y" field footer), styles (custom paragraph styles with
font, size, bold/italic, hex color), and blocks — heading
(level 1-9), paragraph (either text or a runs list where each run
may set bold/italic/underline), bullet_list, numbered_list,
table (header row rendered bold, rows, optional built-in table
style such as Table Grid), image (path, optional width_mm),
toc (Table of Contents field), and page_break. The full spec
format is documented at the top of scripts/docx_create.py.
- Read. Use
scripts/docx_read.py with exactly one mode flag.
--text returns body paragraphs, all table cell text, and
header/footer text as JSON. --structure returns the heading outline
plus paragraph/table/section counts. --images DIR copies every file
under word/media/ out of the package.
- Edit. Use
scripts/docx_edit.py. replace walks body, tables
(nested included), headers and footers, and preserves run formatting;
add --body-only to skip headers/footers. Pass -o out.docx to keep
the original; omit it to edit in place. Paragraph indices for
insert/delete/style/toc refer to --structure/--text body
order. Run normalize first on documents that came out of heavy Word
editing — it merges adjacent runs with identical formatting so later
find-replace matches reliably.
- Review revisions.
docx_revisions.py list reports every w:ins
and w:del (id, author, date, affected text) anywhere in body,
tables, headers, or footers. accept-all / reject-all resolve them
in bulk; accept/reject --id N handles a single revision. Accept
keeps insertions and drops deleted text; reject does the reverse.
- Comments.
docx_comments.py list returns each comment's id,
author, date, body text, and the document text it is anchored to.
add --target "some phrase" anchors a new comment to the first
occurrence of that phrase (runs are split as needed; formatting is
preserved). delete --id N removes the comment and its markers
without touching document text.
- Template. Put
{{name}}-style tokens in the document. Run
scripts/docx_template.py with a JSON object of values. Use
--strict to fail when tokens remain unfilled; the JSON output lists
filled counts and unfilled_tokens either way.
- Verify (always): re-read the output with
--text or
--structure, and run docx_validate.py on anything you produced
via revision/comment surgery.
Converting to PDF
No script needed. When LibreOffice is installed, convert headlessly:
soffice --headless --convert-to pdf --outdir outdir/ file.docx
Check availability first (command -v soffice || command -v libreoffice). If neither exists, tell the user PDF conversion is
unavailable in this environment rather than improvising — python-docx
cannot render PDFs, and layout fidelity requires a real renderer.
Pitfalls
- Tokens split across runs. Word often fragments text into several
runs. The replace helpers collapse matched runs (replacement inherits
the first run's formatting); running
docx_edit.py normalize first
reduces fragmentation for all later edits.
- Revision coverage.
docx_revisions.py resolves run-level
insertions and deletions (the overwhelming majority). Paragraph-mark
and table-row revisions, format-change records, and moves are detected
by --revisions but not auto-resolved — see
references/revisions-and-comments.md and hand those to Word.
- Comment threading. Replies and "resolved" status live in
commentsExtended.xml, which this skill ignores; comments it adds are
plain top-level comments.
- Field results are computed by Word.
toc, page-numbers, and the
toc/footer_page_numbers spec options write field codes.
Word/LibreOffice populates the actual entries and numbers when the
file is opened (Word may prompt to update fields); python-docx never
computes them, so placeholder text shows until then.
- Validation is a health check, not schema validation.
docx_validate.py verifies the zip, required parts, relationship
targets, image magic bytes, and referenced styles. It is NOT XSD
validation — a file can pass and still contain XML Word dislikes.
- Style names must exist. Applying a style that isn't defined in the
document raises
KeyError. Built-ins like Heading 1, List Bullet,
List Number, Table Grid exist in the default template; custom
styles must be declared in the create spec first.
- Numbered lists restart.
List Number relies on Word's default
numbering; separate lists in one document may continue numbering
instead of restarting. Warn users needing precise multi-list numbering.
- Cell writes replace formatting.
set-cell uses cell.text = ...,
which resets runs in that cell to plain formatting.
- Encoding. All JSON specs/values files are read as UTF-8 explicitly;
never rely on locale defaults when writing your own glue code.
- Don't unzip-and-sed the XML. Edit through the scripts (or
python-docx); raw text substitution in
document.xml corrupts files
easily. Use patch/write_file only for the JSON inputs, never on the
.docx itself.
Verification
- After create/edit/template, run
docx_read.py out.docx --text and
check the expected strings appear (and old strings are gone).
- After accept/reject,
docx_revisions.py list should return [] (or
only the ids you intentionally left); after comment surgery,
docx_comments.py list should reflect the change and --text output
must be unchanged.
docx_validate.py out.docx exits 0 with "ok": true on a healthy
package — run it after any revision/comment/field manipulation.
- For templates run with
--strict, or check unfilled_tokens == [].
- Structure checks:
--structure should show the expected heading
outline and table shapes; --styles confirms custom styles applied.
1---2name: docx3description: Create, read, edit, template, and review Word .docx files.4license: MIT5---67# Docx Skill89Create, read, edit, and template Microsoft Word `.docx` files with10python-docx via small CLIs. It handles text, styles, lists, tables,11images, headers/footers, `{{token}}` templating, tracked changes12(list/accept/reject), comments (list/add/delete), TOC and page-number13fields, and package health checks. It does not render documents itself14(PDF needs LibreOffice — see Converting to PDF) or edit legacy `.doc`.1516## When to Use1718- The user asks to generate a Word document (report, letter, contract).19- You need the text, outline, styles, or embedded images of a `.docx`.20- You must change an existing `.docx`: replace text, edit table cells,21 insert/delete paragraphs, apply styles, merge fragmented runs.22- You have a `.docx` template with `{{placeholders}}` to fill from data.23- The document has tracked changes to review, accept, or reject.24- You need to read reviewers' comments, or add/delete comments.25- A `.docx` won't open or behaves oddly and you need corruption triage.26- The document needs a table of contents or "Page X of Y" footers.27- Not for: `.doc` (legacy), `.odt`, or WYSIWYG layout work.2829## Prerequisites3031- Python 3.10+ with `python-docx` installed:32 `pip install python-docx` (import name is `docx`; lxml comes with it).33- Comments `add` uses the native API on python-docx >= 1.2 and an XML34 fallback on older versions — both are automatic.35- For image blocks: the image files must exist locally (PNG/JPEG).3637## How to Run3839All helpers live in `scripts/` next to this file. Run them with the40`terminal` tool; each supports `--help` and prints JSON to stdout.4142```bash43python scripts/docx_create.py spec.json out.docx44python scripts/docx_read.py out.docx --text45python scripts/docx_edit.py replace out.docx --find old --replace new46python scripts/docx_template.py tpl.docx values.json filled.docx47python scripts/docx_revisions.py list out.docx48python scripts/docx_comments.py list out.docx49python scripts/docx_validate.py out.docx50```5152## Quick Reference5354| Task | Command |55| --- | --- |56| Create from JSON spec | `docx_create.py spec.json out.docx` |57| Full text (body+tables+headers/footers) | `docx_read.py f.docx --text` |58| Heading outline + table shapes | `docx_read.py f.docx --structure` |59| Styles actually used | `docx_read.py f.docx --styles` |60| Extract embedded images | `docx_read.py f.docx --images outdir/` |61| Detect tracked changes/comments | `docx_read.py f.docx --revisions` |62| Find/replace (formatting kept) | `docx_edit.py replace f.docx --find A --replace B -o out.docx` |63| Set a table cell | `docx_edit.py set-cell f.docx --table 0 --row 1 --col 2 --text X` |64| Insert paragraph before index N | `docx_edit.py insert f.docx --index N --text X --style Normal` |65| Delete paragraph N | `docx_edit.py delete f.docx --index N` |66| Apply style to paragraph N | `docx_edit.py style f.docx --index N --style "Heading 1"` |67| Merge equal-format adjacent runs | `docx_edit.py normalize f.docx -o out.docx` |68| Insert TOC field before para N | `docx_edit.py toc f.docx --index N -o out.docx` |69| "Page X of Y" footer fields | `docx_edit.py page-numbers f.docx` |70| Fill `{{tokens}}` | `docx_template.py tpl.docx values.json out.docx --strict` |71| List revisions (id/author/date/text) | `docx_revisions.py list f.docx` |72| Accept / reject all revisions | `docx_revisions.py accept-all f.docx -o out.docx` (or `reject-all`) |73| Accept / reject one revision | `docx_revisions.py accept f.docx --id 3 -o out.docx` |74| List comments (+anchored text) | `docx_comments.py list f.docx` |75| Add comment anchored to text | `docx_comments.py add f.docx --target "phrase" --text "note" --author You` |76| Delete comment by id | `docx_comments.py delete f.docx --id 0` |77| Health-check the package | `docx_validate.py f.docx` (exit 1 on errors) |7879## Procedure80811. **Create.** Write a JSON spec with `write_file`, then run82 `scripts/docx_create.py`. The spec supports: `page` (size + margins in83 mm), `header`/`footer` strings, `footer_page_numbers` (adds a84 "Page X of Y" field footer), `styles` (custom paragraph styles with85 font, size, bold/italic, hex `color`), and `blocks` — `heading`86 (level 1-9), `paragraph` (either `text` or a `runs` list where each run87 may set `bold`/`italic`/`underline`), `bullet_list`, `numbered_list`,88 `table` (`header` row rendered bold, `rows`, optional built-in table89 `style` such as `Table Grid`), `image` (`path`, optional `width_mm`),90 `toc` (Table of Contents field), and `page_break`. The full spec91 format is documented at the top of `scripts/docx_create.py`.922. **Read.** Use `scripts/docx_read.py` with exactly one mode flag.93 `--text` returns body paragraphs, all table cell text, and94 header/footer text as JSON. `--structure` returns the heading outline95 plus paragraph/table/section counts. `--images DIR` copies every file96 under `word/media/` out of the package.973. **Edit.** Use `scripts/docx_edit.py`. `replace` walks body, tables98 (nested included), headers and footers, and preserves run formatting;99 add `--body-only` to skip headers/footers. Pass `-o out.docx` to keep100 the original; omit it to edit in place. Paragraph indices for101 `insert`/`delete`/`style`/`toc` refer to `--structure`/`--text` body102 order. Run `normalize` first on documents that came out of heavy Word103 editing — it merges adjacent runs with identical formatting so later104 find-replace matches reliably.1054. **Review revisions.** `docx_revisions.py list` reports every `w:ins`106 and `w:del` (id, author, date, affected text) anywhere in body,107 tables, headers, or footers. `accept-all` / `reject-all` resolve them108 in bulk; `accept`/`reject --id N` handles a single revision. Accept109 keeps insertions and drops deleted text; reject does the reverse.1105. **Comments.** `docx_comments.py list` returns each comment's id,111 author, date, body text, and the document text it is anchored to.112 `add --target "some phrase"` anchors a new comment to the first113 occurrence of that phrase (runs are split as needed; formatting is114 preserved). `delete --id N` removes the comment and its markers115 without touching document text.1166. **Template.** Put `{{name}}`-style tokens in the document. Run117 `scripts/docx_template.py` with a JSON object of values. Use118 `--strict` to fail when tokens remain unfilled; the JSON output lists119 `filled` counts and `unfilled_tokens` either way.1207. **Verify** (always): re-read the output with `--text` or121 `--structure`, and run `docx_validate.py` on anything you produced122 via revision/comment surgery.123124## Converting to PDF125126No script needed. When LibreOffice is installed, convert headlessly:127128```bash129soffice --headless --convert-to pdf --outdir outdir/ file.docx130```131132Check availability first (`command -v soffice || command -v133libreoffice`). If neither exists, tell the user PDF conversion is134unavailable in this environment rather than improvising — python-docx135cannot render PDFs, and layout fidelity requires a real renderer.136137## Pitfalls138139- **Tokens split across runs.** Word often fragments text into several140 runs. The replace helpers collapse matched runs (replacement inherits141 the first run's formatting); running `docx_edit.py normalize` first142 reduces fragmentation for all later edits.143- **Revision coverage.** `docx_revisions.py` resolves run-level144 insertions and deletions (the overwhelming majority). Paragraph-mark145 and table-row revisions, format-change records, and moves are detected146 by `--revisions` but not auto-resolved — see147 `references/revisions-and-comments.md` and hand those to Word.148- **Comment threading.** Replies and "resolved" status live in149 `commentsExtended.xml`, which this skill ignores; comments it adds are150 plain top-level comments.151- **Field results are computed by Word.** `toc`, `page-numbers`, and the152 `toc`/`footer_page_numbers` spec options write *field codes*.153 Word/LibreOffice populates the actual entries and numbers when the154 file is opened (Word may prompt to update fields); python-docx never155 computes them, so placeholder text shows until then.156- **Validation is a health check, not schema validation.**157 `docx_validate.py` verifies the zip, required parts, relationship158 targets, image magic bytes, and referenced styles. It is NOT XSD159 validation — a file can pass and still contain XML Word dislikes.160- **Style names must exist.** Applying a style that isn't defined in the161 document raises `KeyError`. Built-ins like `Heading 1`, `List Bullet`,162 `List Number`, `Table Grid` exist in the default template; custom163 styles must be declared in the create spec first.164- **Numbered lists restart.** `List Number` relies on Word's default165 numbering; separate lists in one document may continue numbering166 instead of restarting. Warn users needing precise multi-list numbering.167- **Cell writes replace formatting.** `set-cell` uses `cell.text = ...`,168 which resets runs in that cell to plain formatting.169- **Encoding.** All JSON specs/values files are read as UTF-8 explicitly;170 never rely on locale defaults when writing your own glue code.171- **Don't unzip-and-sed the XML.** Edit through the scripts (or172 python-docx); raw text substitution in `document.xml` corrupts files173 easily. Use `patch`/`write_file` only for the JSON inputs, never on the174 `.docx` itself.175176## Verification177178- After create/edit/template, run `docx_read.py out.docx --text` and179 check the expected strings appear (and old strings are gone).180- After accept/reject, `docx_revisions.py list` should return `[]` (or181 only the ids you intentionally left); after comment surgery,182 `docx_comments.py list` should reflect the change and `--text` output183 must be unchanged.184- `docx_validate.py out.docx` exits 0 with `"ok": true` on a healthy185 package — run it after any revision/comment/field manipulation.186- For templates run with `--strict`, or check `unfilled_tokens == []`.187- Structure checks: `--structure` should show the expected heading188 outline and table shapes; `--styles` confirms custom styles applied.