How to Use OfficeCLI Word Content Controls for Structured Document Templates
OfficeCLI Word content controls enable programmatic creation of fillable Word templates by inserting Structured Document Tags (SDTs) with configurable types, aliases, and validation rules via a Python SDK that communicates through a named pipe.
The iOfficeAI/OfficeCLI repository provides a command-line interface and Python SDK for automating Microsoft Office documents. By leveraging OfficeCLI Word content controls, developers can build reusable, data-driven templates that support programmatic population, validation, and extraction of structured content without manual document editing.
Understanding Content Controls and SDTs
Content controls (also called Structured Document Tags or SDTs) are bounded regions in Word documents that function as single, fillable form fields. In OfficeCLI, each SDT is defined by a consistent set of core properties (alias, tag, lock, placeholderText) and type-specific configurations that determine its behavior and validation rules.
The SDK operates by serializing commands as JSON and transmitting them to a resident officecli process via a platform-specific named pipe. This architecture, implemented in sdk/python/officecli.py, ensures low-latency communication while maintaining automatic process resilience—the SDK restarts dead residents and retries commands once without manual intervention.
Creating a Document with Content Controls
Initializing the Document Handle
All operations begin by spawning a resident process and obtaining a Document handle. The officecli.create() function accepts a file path and optional flags, returning a context manager that ensures proper cleanup.
import officecli # pip install officecli-sdk
FILE = "my-template.docx"
with officecli.create(FILE, "--force") as doc:
# Document operations execute here
pass
Adding Structured Document Tags
Insert content controls using the sdt() helper function or raw command dictionaries. The doc.batch([...]) method sends multiple additions in a single round-trip, minimizing latency when building complex templates.
Each SDT requires a type parameter specifying the control variant. Supported types include:
- text: Plain-text input fields
- dropdown: Fixed selection lists
- combobox: Editable selection lists with preset options
- date: Calendar picker with configurable formatting
- picture: Image placeholder controls
- richtext: Formatted text regions
- group: Container controls that lock nested content
- checkbox: Boolean selection controls
def sdt(**props):
return {"command": "add", "parent": "/body", "type": "sdt", "props": props}
# Plain-text control with placeholder
doc.batch([sdt(type="text", alias="Full Name", tag="fullName",
text="[Enter full legal name]", lock="unlocked",
placeholderText="DefaultPlaceholder")])
Configuring Type-Specific Properties
Different SDT types accept specialized properties beyond the core set. Dropdown and combobox controls require an items string (comma-separated values with optional pipe-delimited aliases), while date controls accept format, calendar, and lid (locale ID) parameters.
# Dropdown with preset options
doc.batch([sdt(type="dropdown", alias="Department", tag="department",
items="Sales,Engineering,Human Resources,Finance,Operations",
**{"dropDown.lastValue": "Engineering"})])
# Date picker with ISO format
doc.batch([sdt(type="date", alias="Start Date", tag="startDate",
format="yyyy-MM-dd",
**{"date.fullDate": "2026-02-01T00:00:00Z",
"date.calendar": "gregorian",
"date.lid": "en-US"})])
Modifying and Reading Back Controls
After creation, mutable properties (alias, tag, lock, text) can be updated using the set command, while type-specific properties like dropDown.lastValue remain immutable once established.
# Rename a control and lock its structure
doc.send({"command": "set", "path": "/body/sdt[2]",
"props": {"alias": "Home Department", "lock": "sdtLocked"}})
To extract data or verify template structure, use the get command with XPath-like paths. The response includes canonical properties for each control, enabling validation or further processing.
# Read back properties from the third SDT
node = doc.send({"command": "get", "path": "/body/sdt[3]"})
fmt = node.get("data", {}).get("results", [{}])[0].get("format", {})
print(f"Type: {fmt.get('type')}, Alias: {fmt.get('alias')}")
Complete Working Example
The file examples/word/content-controls.py demonstrates the full lifecycle: creating a document, inserting eight different SDT types, modifying properties post-creation, saving, and iterating through controls to read their properties. The example also invokes the CLI validator to ensure schema compliance.
import os
import officecli
FILE = os.path.join(os.path.dirname(__file__), "my-template.docx")
def para(text, **props):
return {"command": "add", "parent": "/body", "type": "paragraph",
"props": {"text": text, **props}}
def sdt(**props):
return {"command": "add", "parent": "/body", "type": "sdt", "props": props}
with officecli.create(FILE, "--force") as doc:
# Document metadata
doc.batch([para("Employee Intake Form", style="Title"),
para("Fill each field – grey boxes are content controls.", style="Subtitle")])
# 1. Plain-text input
doc.batch([para("Full name", style="Heading2"),
sdt(type="text", alias="Full Name", tag="fullName",
text="[Enter full legal name]", lock="unlocked")])
# 2. Dropdown selection
doc.batch([para("Department", style="Heading2"),
sdt(type="dropdown", alias="Department", tag="department",
items="Sales,Engineering,Human Resources",
**{"dropDown.lastValue": "Engineering"})])
# 3. Combo-box with aliases
doc.batch([para("Primary office", style="Heading2"),
sdt(type="combobox", alias="Office Location", tag="office",
items="New York|NYC,London|LON,Singapore|SIN",
**{"comboBox.lastValue": "LON"})])
# 4. Date picker
doc.batch([para("Start date", style="Heading2"),
sdt(type="date", alias="Start Date", tag="startDate",
format="yyyy-MM-dd",
**{"date.fullDate": "2026-02-01T00:00:00Z",
"date.calendar": "gregorian",
"date.lid": "en-US"})])
# 5. Picture placeholder
doc.batch([para("Profile photo", style="Heading2"),
sdt(type="picture", alias="Profile Photo", tag="photo")])
# 6. Rich-text (locked content)
doc.batch([para("Reviewer notes", style="Heading2"),
sdt(type="richtext", alias="Reviewer Notes", tag="notes",
text="Manager may add formatted commentary here.", lock="contentLocked")])
# 7. Group container
doc.batch([para("Approval", style="Heading2"),
sdt(type="group", alias="Approval Block", tag="approval",
text="Approved by HR — signature on file.", lock="sdtContentLocked")])
# 8. Checkbox
doc.batch([para("HR approved", style="Heading2"),
sdt(type="checkbox", alias="Approved", tag="hrApproved", checked="true")])
# Modify existing control
doc.send({"command": "set", "path": "/body/sdt[2]",
"props": {"alias": "Home Department", "lock": "sdtLocked"}})
# Persist document
doc.send({"command": "save"})
# Read back all controls
for i in range(1, 9):
node = doc.send({"command": "get", "path": f"/body/sdt[{i}]"})
fmt = node.get("data", {}).get("results", [{}])[0].get("format", {})
print(f"sdt[{i}] type={fmt.get('type')} alias={fmt.get('alias')!r}")
Summary
- OfficeCLI Word content controls are implemented as Structured Document Tags (SDTs) that support types including text, dropdown, combobox, date, picture, richtext, group, and checkbox.
- The Python SDK in
sdk/python/officecli.pyuses a pipe-based RPC transport to send JSON commands to a resident process, with automatic restart capabilities for resilience. - Use
officecli.create()to initialize documents,doc.batch()for efficient multi-control insertion, anddoc.send()with commandsset,get, andsavefor manipulation and persistence. - Core properties (
alias,tag,lock) remain mutable after creation via thesetcommand, while type-specific properties must be defined during initial SDT construction. - The reference implementation in
examples/word/content-controls.pyprovides a complete template generation workflow including validation.
Frequently Asked Questions
What are content controls in OfficeCLI Word documents?
Content controls (SDTs) are bounded, programmable regions within Word documents that act as structured form fields. According to the iOfficeAI/OfficeCLI source code, these controls encapsulate specific data types and validation rules, allowing developers to create templates where users can only input data in predefined formats, such as dates from a calendar picker or selections from a dropdown list.
How do I modify an existing content control after creation?
Use the set command with the control's XPath path to update mutable properties. The implementation in sdk/python/officecli.py supports modifying alias, tag, lock, and text properties post-creation via doc.send({"command": "set", "path": "/body/sdt[<index>]", "props": {...}}). Note that type-specific properties like dropdown items or date formats are immutable after the SDT is inserted and must be configured during initial creation.
What SDT types does OfficeCLI support?
OfficeCLI supports eight primary SDT types as demonstrated in examples/word/content-controls.py: text (plain input), dropdown (fixed lists), combobox (editable lists with presets), date (calendar controls with format strings), picture (image placeholders), richtext (formatted text regions), group (container controls with locking capabilities), and checkbox (boolean toggles). Each type accepts specific configuration parameters in addition to the standard alias and tag properties.
How does the OfficeCLI Python SDK communicate with Word?
The SDK communicates through a platform-specific named pipe to a resident officecli process that handles the actual Word interop. As implemented in the Document._cmd method, commands are serialized as JSON, transmitted via the pipe, and responses are parsed back into Python objects. This architecture eliminates the overhead of spawning new processes for each command and includes automatic retry logic that restarts dead residents once before failing.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →