# How to Use OfficeCLI Word Content Controls for Structured Document Templates

> Learn to use OfficeCLI Word content controls for structured document templates. Programmatically create fillable templates with configurable SDTs using a Python SDK.

- Repository: [OfficeAI/OfficeCLI](https://github.com/iofficeai/OfficeCLI)
- Tags: how-to-guide
- Published: 2026-08-09

---

**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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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.

```python
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

```python
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.

```python

# 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.

```python

# 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.

```python

# 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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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.

```python
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.py`](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/python/officecli.py) uses 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, and `doc.send()` with commands `set`, `get`, and `save` for manipulation and persistence.
- Core properties (`alias`, `tag`, `lock`) remain mutable after creation via the `set` command, while type-specific properties must be defined during initial SDT construction.
- The reference implementation in [`examples/word/content-controls.py`](https://github.com/iOfficeAI/OfficeCLI/blob/main/examples/word/content-controls.py) provides 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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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`](https://github.com/iOfficeAI/OfficeCLI/blob/main/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.