# How to Add Comments to DOCX Files Using XML Manipulation: A Complete Guide

> Learn how to add comments to DOCX files with Python by directly editing OpenXML. This guide uses a pure-Python utility, no MS Office needed.

- Repository: [Anthropic/skills](https://github.com/anthropics/skills)
- Tags: how-to-guide
- Published: 2026-02-16

---

**The anthropics/skills repository provides a pure-Python utility that inserts Word comments by directly editing the underlying OpenXML parts of a DOCX file without requiring Microsoft Office libraries.**

Adding comments to DOCX files using XML manipulation allows you to programmatically annotate Word documents at the byte level. This approach unpacks the DOCX archive—a ZIP file containing XML parts—and modifies the OpenXML structure directly, offering precision that high-level libraries cannot match.

## Understanding the DOCX Comment Architecture

### The OpenXML Package Structure

A DOCX file is essentially a ZIP archive containing multiple XML parts. When you add comments to DOCX files using XML manipulation, you work with an **unpacked** directory structure that includes:

- [`word/document.xml`](https://github.com/anthropics/skills/blob/main/word/document.xml) – The main document content
- [`word/comments.xml`](https://github.com/anthropics/skills/blob/main/word/comments.xml) – The actual comment text and metadata
- [`word/commentsExtended.xml`](https://github.com/anthropics/skills/blob/main/word/commentsExtended.xml) – Threading information for replies
- [`word/commentsIds.xml`](https://github.com/anthropics/skills/blob/main/word/commentsIds.xml) – Mapping between paragraph IDs and durable IDs
- [`word/commentsExtensible.xml`](https://github.com/anthropics/skills/blob/main/word/commentsExtensible.xml) – Creation timestamps for extensible comments
- `word/_rels/document.xml.rels` – Relationships defining connections between parts
- `[Content_Types].xml` – MIME type declarations for all document parts

### Required XML Components for Comments

Every comment requires two distinct identifiers generated by the `_generate_hex_id()` function in [`skills/docx/scripts/comment.py`](https://github.com/anthropics/skills/blob/main/skills/docx/scripts/comment.py):

1. **Comment ID (`cid`)** – An 8-digit hexadecimal value used for markers in [`document.xml`](https://github.com/anthropics/skills/blob/main/document.xml)
2. **Durable ID** – A separate 8-digit hexadecimal value for cross-referencing in [`commentsIds.xml`](https://github.com/anthropics/skills/blob/main/commentsIds.xml)

The system also maintains relationship entries in `word/_rels/document.xml.rels` and content-type overrides in `[Content_Types].xml` to ensure the document remains valid.

## Step-by-Step XML Manipulation Process

### Step 1: Unpack the DOCX Archive

Before you can add comments to DOCX files using XML manipulation, you must extract the ZIP archive:

```bash
unzip document.docx -d unpacked_docx/

```

This creates a directory structure where individual XML parts can be edited directly.

### Step 2: Generate Unique Identifiers

The `_generate_hex_id()` function in [`skills/docx/scripts/comment.py`](https://github.com/anthropics/skills/blob/main/skills/docx/scripts/comment.py) creates the necessary hexadecimal identifiers:

```python
from skills.docx.scripts.comment import _generate_hex_id

para_id = _generate_hex_id()      # For paragraph identification

durable_id = _generate_hex_id()   # For durable cross-references

```

Both values are 8-digit hexadecimal strings used throughout the OpenXML parts.

### Step 3: Ensure Relationship and Content-Type Entries

The utility automatically checks and updates package infrastructure:

- **`_ensure_comment_relationships()`** – Adds `<Relationship>` elements to `word/_rels/document.xml.rels` if comment parts are not already referenced
- **`_ensure_comment_content_types()`** – Inserts `<Override>` elements into `[Content_Types].xml` to declare MIME types for [`comments.xml`](https://github.com/anthropics/skills/blob/main/comments.xml), [`commentsExtended.xml`](https://github.com/anthropics/skills/blob/main/commentsExtended.xml), and related parts

These functions prevent "part not found" errors when Word opens the document.

### Step 4: Insert Comment XML Fragments

The `_append_xml()` helper function in [`skills/docx/scripts/comment.py`](https://github.com/anthropics/skills/blob/main/skills/docx/scripts/comment.py) handles the actual XML insertion. It:

1. Parses the target XML file
2. Wraps new fragments in a temporary `<root>` element with proper namespace declarations
3. Appends child nodes to the appropriate parent element
4. Re-serializes the DOM and re-escapes smart-quote entities via `_encode_smart_quotes()`

The comment body uses the `COMMENT_XML` template to populate [`comments.xml`](https://github.com/anthropics/skills/blob/main/comments.xml) with author, timestamp, and text data.

### Step 5: Add Comment Markers to document.xml

Unlike automated insertion, markers must be manually placed. The script outputs a snippet using `COMMENT_MARKER_TEMPLATE` or `REPLY_MARKER_TEMPLATE`:

```xml
<w:commentRangeStart w:id="0"/>
<!-- commented content -->
<w:commentRangeEnd w:id="0"/>
<w:r>
  <w:rPr>
    <w:rStyle w:val="CommentReference"/>
  </w:rPr>
  <w:commentReference w:id="0"/>
</w:r>

```

**Critical constraint**: Markers must be **direct children of `<w:p>`** elements—never nested inside `<w:r>` tags. The `validate_comment_markers()` function in [`skills/xlsx/scripts/office/validators/docx.py`](https://github.com/anthropics/skills/blob/main/skills/xlsx/scripts/office/validators/docx.py) enforces this rule (lines 298-384).

### Step 6: Validate the Result

After repacking the DOCX, run the validator to ensure integrity:

```python
from skills.xlsx.scripts.office.validators.docx import validate_comment_markers

# Validates that each commentRangeStart has matching commentRangeEnd

# and that IDs correspond to real comments in comments.xml

validate_comment_markers("unpacked_docx/")

```

## Practical Implementation Using the Skills Utility

The `add_comment()` function in [`skills/docx/scripts/comment.py`](https://github.com/anthropics/skills/blob/main/skills/docx/scripts/comment.py) encapsulates the entire workflow. It returns the generated `para_id` (used for threading replies) and a status message.

### Adding a Top-Level Comment

```bash
python skills/docx/scripts/comment.py unpacked/ 0 "Review this paragraph for accuracy" \
    --author "Alice Smith" \
    --initials "AS"

```

This generates:
- A new entry in [`comments.xml`](https://github.com/anthropics/skills/blob/main/comments.xml) with ID 0
- Relationship entries if missing
- A marker snippet to paste into [`document.xml`](https://github.com/anthropics/skills/blob/main/document.xml)

### Adding Threaded Replies

To create a reply to an existing comment, use the `--parent` flag with the parent comment's `para_id`:

```bash
python skills/docx/scripts/comment.py unpacked/ 1 "I agree with this assessment" \
    --author "Bob Jones" \
    --initials "BJ" \
    --parent <para_id_from_parent>

```

The script outputs a `REPLY_MARKER_TEMPLATE` that nests the reply markers within the parent's context in [`commentsExtended.xml`](https://github.com/anthropics/skills/blob/main/commentsExtended.xml).

## Key Files and Functions Reference

| Path | Purpose |
|------|---------|
| [`skills/docx/scripts/comment.py`](https://github.com/anthropics/skills/blob/main/skills/docx/scripts/comment.py) | Core utility containing `add_comment()`, `_generate_hex_id()`, `_ensure_comment_relationships()`, `_ensure_comment_content_types()`, and `_append_xml()` |
| [`skills/docx/scripts/templates/comments.xml`](https://github.com/anthropics/skills/blob/main/skills/docx/scripts/templates/comments.xml) | Minimal starter template used when initializing [`comments.xml`](https://github.com/anthropics/skills/blob/main/comments.xml) for the first time |
| [`skills/xlsx/scripts/office/validators/docx.py`](https://github.com/anthropics/skills/blob/main/skills/xlsx/scripts/office/validators/docx.py) | Validation logic including `validate_comment_markers()` (lines 298-384) that enforces proper marker placement and ID matching |
| `word/_rels/document.xml.rels` | Relationship part defining connections between [`document.xml`](https://github.com/anthropics/skills/blob/main/document.xml) and comment parts |
| `[Content_Types].xml` | Package content type declarations required for all comment-related XML parts |

## Summary

- **Unpack first**: DOCX files are ZIP archives that must be extracted before XML manipulation.
- **ID management**: Every comment requires two 8-digit hexadecimal IDs generated by `_generate_hex_id()` for cross-referencing between parts.
- **Infrastructure checks**: Use `_ensure_comment_relationships()` and `_ensure_comment_content_types()` to maintain valid package structure.
- **Manual marker placement**: Comment markers must be pasted manually into [`document.xml`](https://github.com/anthropics/skills/blob/main/document.xml) as direct children of `<w:p>` elements, never nested inside `<w:r>` tags.
- **Validation**: Run `validate_comment_markers()` from [`skills/xlsx/scripts/office/validators/docx.py`](https://github.com/anthropics/skills/blob/main/skills/xlsx/scripts/office/validators/docx.py) to ensure all comment ranges have matching start/end pairs and valid ID references.

## Frequently Asked Questions

### Can I add comments without unpacking the DOCX file?

No, the [`skills/docx/scripts/comment.py`](https://github.com/anthropics/skills/blob/main/skills/docx/scripts/comment.py) utility requires an unpacked directory structure because it performs direct file system operations on individual XML parts like [`word/document.xml`](https://github.com/anthropics/skills/blob/main/word/document.xml) and [`word/comments.xml`](https://github.com/anthropics/skills/blob/main/word/comments.xml). You must first extract the DOCX using `unzip` or similar tools, run the manipulation scripts, and then repackage the directory contents into a ZIP file with the `.docx` extension.

### What are the risks of manual XML manipulation?

The primary risk is producing invalid OpenXML that Microsoft Word cannot open. Common failure modes include: missing relationship entries in `word/_rels/document.xml.rels` (solved by `_ensure_comment_relationships()`), missing content-type declarations in `[Content_Types].xml` (solved by `_ensure_comment_content_types()`), and improperly nested comment markers inside `<w:r>` elements rather than as direct children of `<w:p>` (detected by `validate_comment_markers()` in [`skills/xlsx/scripts/office/validators/docx.py`](https://github.com/anthropics/skills/blob/main/skills/xlsx/scripts/office/validators/docx.py)).

### How do I add threaded replies to existing comments?

To create a reply, invoke `add_comment()` with the `--parent` flag set to the `para_id` value returned when the parent comment was created. The script generates a `REPLY_MARKER_TEMPLATE` snippet that you paste into [`document.xml`](https://github.com/anthropics/skills/blob/main/document.xml) near the parent comment markers. Internally, this creates a `<w15:commentEx>` entry in [`commentsExtended.xml`](https://github.com/anthropics/skills/blob/main/commentsExtended.xml) linking the reply to its parent, enabling Word's threaded conversation view.

### Is Microsoft Office required to validate the modified DOCX?

No, the `skills` repository includes a standalone validator in [`skills/xlsx/scripts/office/validators/docx.py`](https://github.com/anthropics/skills/blob/main/skills/xlsx/scripts/office/validators/docx.py) that checks document integrity without requiring Microsoft Office installation. The `validate_comment_markers()` function specifically verifies that every `<w:commentRangeStart>` has a corresponding `<w:commentRangeEnd>` with matching IDs, and that these IDs reference actual comments defined in [`comments.xml`](https://github.com/anthropics/skills/blob/main/comments.xml). This allows you to verify document integrity in CI/CD pipelines or server environments where Office is not available.