# When to Use Stable ID Paths (`@attr=value`) in OfficeCLI Instead of Positional Indices

> Master OfficeCLI by choosing stable ID paths over positional indices for reliable automation and multi-step workflows. Learn when to use @attr=value for robust document editing.

- Repository: [OfficeAI/OfficeCLI](https://github.com/iofficeai/OfficeCLI)
- Tags: best-practices
- Published: 2026-07-08

---

**Use stable ID paths (`@attr=value`) for any multi-step workflow or automation script where document edits might shift positional indices, and reserve positional indices (`[n]`) only for elements that lack persistent OOXML identifiers like `@id`, `@paraId`, or `@name`.**

OfficeCLI is an open-source command-line tool for manipulating Office documents (Word, PowerPoint, Excel) via XML-style addressing. When targeting elements inside `.docx`, `.pptx`, or `.xlsx` files, the CLI generates two types of paths: fragile positional indices and **stable ID paths** that persist across document changes. Understanding when to use each ensures your automation scripts remain robust against edits.

## Understanding XML Path Types in OfficeCLI

OfficeCLI addresses elements using XML-style paths that fall into two distinct categories based on the underlying OOXML schema.

### Positional Indices

When an element lacks a persistent identifier, OfficeCLI falls back to positional syntax like `/slide[3]/shape[2]`. These indices reflect the current ordinal position within the parent collection and are inherently fragile—inserting or deleting a sibling element shifts the numeric index, breaking any saved reference.

### Stable ID Paths

When an element exposes a persistent attribute—such as `@id`, `@paraId`, `@commentId`, or `@name`—OfficeCLI generates a stable path like `/slide[1]/shape[@id=550950021]`. According to the source code in [`SchemaHelpRenderer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/SchemaHelpRenderer.cs) (lines 85-92), the CLI merges both `paths.stable` and `paths.positional` lists for user output, but the stable form survives insertions, deletions, and re-ordering.

## When to Use Stable ID Paths

### Robustness Across Document Edits

Adding a paragraph before an existing one shifts positional indices—`/body/p[4]` becomes `/body/p[5]`—but the stable path `/body/p[@paraId=1A2B3C4D]` remains valid. As implemented in [`WordHandler.Add.Misc.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/WordHandler.Add.Misc.cs) (lines 1660-2341), Word paragraphs expose `@paraId` and `@sdtId` attributes, while PowerPoint shapes expose `@id` and `@name` via [`PowerPointHandler.Helpers.ShapeId.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/PowerPointHandler.Helpers.ShapeId.cs) (lines 90-183).

### Concurrency Safety

When multiple scripts or agents interact with the same document, stable IDs guarantee addressability even if parallel edits reorder elements. The underlying OOXML object retains its identifier regardless of sibling changes, ensuring that `/body/p[@paraId=1A2B3C4D]` always points to the same logical paragraph.

### Cross-Command Composition

Stable paths enable you to capture a path from a `get` or `query` command and reuse it directly in a subsequent `set` or `mark` command without recomputing indices. This is critical for watch-mode workflows where the `selected` command returns stable `@id=` paths that can be fed directly into `set` operations.

## When Positional Indices Are Unavoidable

Reserve positional syntax for elements that genuinely lack stable attributes. In Excel, many cell-level operations use positional references like `/Sheet1/B2` because rows and cells lack native persistent identifiers. Similarly, newly inserted table rows or PowerPoint slides may only have numeric indices until the document is saved and stable IDs are generated.

According to the user guide in [`SKILL.md`](https://github.com/iOfficeAI/OfficeCLI/blob/main/SKILL.md), you should store positional paths only for the duration of the immediate operation and never persist them for later automation.

## Practical Code Examples

### Querying Stable Paths in Word

List paragraphs with their stable IDs to identify persistent references:

```bash
officecli get report.docx '/body/p' --json | jq -r '.data.Results[] | "\(.path)  \(.text)"'

```

Sample output showing the `@paraId` attribute:

```text
/body/p[@paraId=1A2B3C4D]  This is the first paragraph.

```

### Using Stable Paths for Updates

Modify a specific paragraph identified by its stable ID. This command succeeds even after other paragraphs are inserted before the target:

```bash
officecli set report.docx '/body/p[@paraId=1A2B3C4D]' --prop font=Helvetica

```

### PowerPoint Shape Targeting

Highlight a shape by its stable ID. If you later add a new shape before the target, the numeric index (`shape[2]`) would shift, but the `@id` path remains correct:

```bash
officecli set deck.pptx '/slide[1]/shape[@id=550950021]' --prop fill=FF0000

```

### Fallback to Positional for Slides

The slide itself has no stable ID; use positional index and accept that reordering will invalidate the reference:

```bash
officecli set deck.pptx '/slide[3]' --prop transition=fade

```

### Watch Mode with Stable Selection

In a live watch session, capture selected shapes using stable paths to ensure subsequent operations target the correct elements:

```bash
PATHS=$(officecli get deck.pptx selected --json | jq -r '.data.Results[].path')
for p in $PATHS; do
  officecli set deck.pptx "$p" --prop fill=00FF00
done

```

The `selected` command always returns **stable `@id=` paths**, guaranteeing that the subsequent `set` affects the same visual element even if the document is edited in the meantime.

## Summary

- **Stable ID paths** (`@attr=value`) use persistent OOXML attributes like `@id`, `@paraId`, and `@name` that survive document edits.
- **Positional indices** (`[n]`) rely on ordinal position and break when siblings are inserted or removed.
- **Word** paragraphs use `@paraId` and `@sdtId` (see [`WordHandler.Add.Misc.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/WordHandler.Add.Misc.cs)), while **PowerPoint** shapes use `@id` and `@name` (see [`PowerPointHandler.Helpers.ShapeId.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/PowerPointHandler.Helpers.ShapeId.cs)).
- Use stable paths for multi-step workflows, automation scripts, and any operation where the document might change between commands.
- Reserve positional indices for elements that truly lack stable identifiers, such as Excel cells or unsaved table rows.

## Frequently Asked Questions

### What makes a stable ID path different from a positional index?

A stable ID path uses an attribute embedded in the OOXML specification—such as `@paraId` in Word or `@id` in PowerPoint—that remains constant even when the document structure changes. A positional index like `/body/p[4]` simply counts elements from the top, so inserting a new paragraph at position 2 shifts all subsequent indices, breaking saved references.

### Can I rely on stable IDs across different OfficeCLI versions?

Yes. Stable IDs derive from the underlying Office Open XML standard attributes (e.g., `@paraId`, `@id`), not from OfficeCLI-specific logic. As long as the document format remains consistent, the stable paths generated by [`SchemaHelpRenderer.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/SchemaHelpRenderer.cs) will remain valid across CLI versions.

### Why do some Excel elements only show positional indices?

Many Excel elements, particularly individual cells and rows, lack native persistent identifiers in the OOXML schema. While charts and drawings expose `@id`, standard cell references like `/Sheet1/B2` are inherently positional based on row and column coordinates. In these cases, OfficeCLI falls back to positional syntax because no stable attribute exists on the element.

### How do I convert a positional path to a stable ID path?

Run a `get` or `query` command against the parent collection to retrieve the stable identifier. For example, `officecli get doc.docx '/body/p'` returns both the positional and stable forms. Extract the `@paraId=` or `@id=` path from the JSON output and use that in subsequent `set` or `mark` commands to ensure durability across edits.