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

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:

Required XML Components for Comments

Every comment requires two distinct identifiers generated by the _generate_hex_id() function in skills/docx/scripts/comment.py:

  1. Comment ID (cid) – An 8-digit hexadecimal value used for markers in document.xml
  2. Durable ID – A separate 8-digit hexadecimal value for cross-referencing in 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:

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 creates the necessary hexadecimal identifiers:

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, 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 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 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:

<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 enforces this rule (lines 298-384).

Step 6: Validate the Result

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

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 encapsulates the entire workflow. It returns the generated para_id (used for threading replies) and a status message.

Adding a Top-Level Comment

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

This generates:

Adding Threaded Replies

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

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.

Key Files and Functions Reference

Path Purpose
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 Minimal starter template used when initializing comments.xml for the first time
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 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 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 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 utility requires an unpacked directory structure because it performs direct file system operations on individual XML parts like word/document.xml and 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).

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 near the parent comment markers. Internally, this creates a <w15:commentEx> entry in 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 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. This allows you to verify document integrity in CI/CD pipelines or server environments where Office is not available.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →