|
| 1 | +--- |
| 2 | +title: Content controls |
| 3 | +sidebarTitle: Content controls |
| 4 | +description: Attach stable, Word-compatible identity to regions of a document and update them programmatically. |
| 5 | +keywords: "content controls, SDT, structured document tags, smart fields, reusable sections, template fields, document automation" |
| 6 | +--- |
| 7 | + |
| 8 | +Content controls are Word's native primitive for **stable, identity-bearing regions** inside a document. They survive Word round-trips, carry app-defined metadata in a `tag` string, and can be discovered, updated, locked, or replaced from any surface that drives SuperDoc: the browser editor, the Node SDK, the CLI, an MCP tool, an AI agent. |
| 9 | + |
| 10 | +In OOXML they are `w:sdt` elements (structured document tags). SuperDoc exposes the full surface under `editor.doc.contentControls.*` and `editor.doc.create.contentControl`. |
| 11 | + |
| 12 | +## Two patterns to start |
| 13 | + |
| 14 | +### Smart fields: one value, every occurrence |
| 15 | + |
| 16 | +Wrap every occurrence of a template variable in an inline text content control sharing the same `tag`. Select by tag, then push the same value to each matching control. |
| 17 | + |
| 18 | +```ts |
| 19 | +// Wrap once, at template-authoring time. |
| 20 | +editor.doc.create.contentControl({ |
| 21 | + kind: 'inline', |
| 22 | + controlType: 'text', |
| 23 | + at: range, |
| 24 | + tag: 'customer', |
| 25 | + alias: 'Customer', |
| 26 | + lockMode: 'unlocked', |
| 27 | +}); |
| 28 | + |
| 29 | +// Push a new value. Every occurrence with tag === 'customer' updates. |
| 30 | +const { items } = editor.doc.contentControls.selectByTag({ tag: 'customer' }); |
| 31 | +for (const { target } of items) { |
| 32 | + editor.doc.contentControls.text.setValue({ target, value: 'Acme Therapeutics' }); |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +Smallest copy-pasteable form: [`examples/document-api/content-controls/tagged-inline-text`](https://github.com/superdoc-dev/superdoc/tree/main/examples/document-api/content-controls/tagged-inline-text). |
| 37 | + |
| 38 | +### Reusable sections: tagged blocks that know their version |
| 39 | + |
| 40 | +Encode `{ sectionId, version }` in the `tag` of a block content control. The app reads the live version from `contentControls.list` and offers an in-place update when the document falls behind the section library. |
| 41 | + |
| 42 | +```ts |
| 43 | +// Wrap a section paragraph as a block content control with a structured tag. |
| 44 | +editor.doc.create.contentControl({ |
| 45 | + kind: 'block', |
| 46 | + controlType: 'text', |
| 47 | + at: range, |
| 48 | + tag: JSON.stringify({ kind: 'reusableSection', sectionId: 'limitation-liability', version: 'v1' }), |
| 49 | + alias: 'Limitation of liability (v1)', |
| 50 | + lockMode: 'unlocked', |
| 51 | +}); |
| 52 | + |
| 53 | +// On reopen: list sections, parse their tags, compare versions. |
| 54 | +const { items } = editor.doc.contentControls.list({}); |
| 55 | +for (const control of items) { |
| 56 | + const meta = parseTag(control.properties?.tag); // your helper |
| 57 | + if (meta?.kind === 'reusableSection' && meta.version !== latestVersionFromLibrary(meta.sectionId)) { |
| 58 | + // Swap content, bump version in tag. |
| 59 | + editor.doc.contentControls.replaceContent({ target: control.target, content: newBody, format: 'text' }); |
| 60 | + editor.doc.contentControls.patch({ |
| 61 | + target: control.target, |
| 62 | + tag: JSON.stringify({ ...meta, version: 'v2' }), |
| 63 | + alias: 'Limitation of liability (v2)', |
| 64 | + }); |
| 65 | + } |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +Composed runtime: [`demos/contract-templates`](https://github.com/superdoc-dev/superdoc/tree/main/demos/contract-templates). |
| 70 | + |
| 71 | +## Why `tag`, not `nodeId` |
| 72 | + |
| 73 | +Two channels of identity exist on a content control: |
| 74 | + |
| 75 | +| Channel | Source | Stable across loads | Stable through Word edits | |
| 76 | +|---|---|---|---| |
| 77 | +| `nodeId` | SuperDoc-assigned at parse time | Best-effort | No | |
| 78 | +| `tag` | App-defined, written to OOXML `<w:tag w:val="...">` | Yes | Yes (Word preserves the SDT and its tag) | |
| 79 | + |
| 80 | +Use `nodeId` for in-session targeting. Use `tag` for durable identity that survives DOCX round-trips, including documents edited in Word and reopened. JSON-encode the `tag` when you need to carry structured metadata (kind, version, owner, group). |
| 81 | + |
| 82 | +## Cross-surface: same operations everywhere |
| 83 | + |
| 84 | +Document API content controls are not editor-specific. The same operation IDs are available on every surface that drives SuperDoc. |
| 85 | + |
| 86 | +| Surface | Binding | |
| 87 | +|---|---| |
| 88 | +| Browser editor | `editor.doc.contentControls.*` | |
| 89 | +| Node SDK | bound document handle methods | |
| 90 | +| CLI | `superdoc` commands | |
| 91 | +| MCP / AI tools | tool wrappers around the same operation IDs | |
| 92 | + |
| 93 | +A field updated by your backend job, a clause swapped by an agent, and a value typed by a user in the editor all hit the same engine. |
| 94 | + |
| 95 | +## When to use Template Builder vs Document API |
| 96 | + |
| 97 | +Two valid paths. Both build on Word content controls. |
| 98 | + |
| 99 | +| Use [Template Builder](/solutions/template-builder/introduction) when | Use Document API content controls when | |
| 100 | +|---|---| |
| 101 | +| You're building in React and want a packaged authoring component | You're on vanilla JS, Vue, Angular, or any non-React stack | |
| 102 | +| You want the `{{` trigger menu, field sidebar, linked field groups, and DOCX export wired up out of the box | You need a custom UX (your own field menu, your own sidebar) | |
| 103 | +| Owner/signer field types and inline custom field creation match your workflow | You're operating headless: server-side jobs, AI agents, CLI scripts | |
| 104 | +| You want a shorter path to a working template authoring UI | You need runtime updates against existing tagged regions (smart fields, version-aware section swaps) | |
| 105 | + |
| 106 | +The two paths are not mutually exclusive. A common pattern is Template Builder for authoring, Document API for runtime updates on the authored document. |
| 107 | + |
| 108 | +## Operation reference at a glance |
| 109 | + |
| 110 | +| Concept | Operation | |
| 111 | +|---|---| |
| 112 | +| Create a control around a range | `editor.doc.create.contentControl` | |
| 113 | +| Wrap an existing range | `editor.doc.contentControls.wrap` | |
| 114 | +| Find by tag | `editor.doc.contentControls.selectByTag` | |
| 115 | +| Find by alias | `editor.doc.contentControls.selectByTitle` | |
| 116 | +| List all controls | `editor.doc.contentControls.list` | |
| 117 | +| Inspect one | `editor.doc.contentControls.get` | |
| 118 | +| Update text value | `editor.doc.contentControls.text.setValue` | |
| 119 | +| Replace whole content | `editor.doc.contentControls.replaceContent` | |
| 120 | +| Patch metadata (tag, alias, appearance) | `editor.doc.contentControls.patch` | |
| 121 | +| Set lock mode | `editor.doc.contentControls.setLockMode` | |
| 122 | +| Delete (with content) | `editor.doc.contentControls.delete` | |
| 123 | +| Unwrap (keep content) | `editor.doc.contentControls.unwrap` | |
| 124 | +| Read `sdtPr` directly | `editor.doc.contentControls.getRawProperties` | |
| 125 | +| Edit `sdtPr` directly | `editor.doc.contentControls.patchRawProperties` | |
| 126 | + |
| 127 | +Typed sub-APIs exist for `text.*`, `date.*`, `checkbox.*`, `choiceList.*` (combo/dropdown), `repeatingSection.*`, and `group.*`. See the [reference index](/document-api/reference/content-controls/index) for the full catalog. |
| 128 | + |
| 129 | +## Lock modes |
| 130 | + |
| 131 | +Set `lockMode` when you create a control to govern which changes are allowed. |
| 132 | + |
| 133 | +| Mode | Behavior | |
| 134 | +|---|---| |
| 135 | +| `unlocked` | Content and properties can be updated through the Document API. | |
| 136 | +| `sdtLocked` | The wrapper is preserved through user edits. | |
| 137 | +| `contentLocked` | The content can't be modified through the editor surface. | |
| 138 | +| `sdtContentLocked` | Both wrapper and content are preserved. | |
| 139 | + |
| 140 | +For controls your app drives with `text.setValue`, `replaceContent`, or `patch`, use `lockMode: 'unlocked'`. |
| 141 | + |
| 142 | +## Data binding |
| 143 | + |
| 144 | +Content controls can carry an OOXML `<w:dataBinding>` link to a custom XML data part. Read and write the binding metadata with `contentControls.getBinding`, `setBinding`, and `clearBinding`. The binding survives DOCX round-trips. |
| 145 | + |
| 146 | +For runtime synchronization with backing data, drive the control directly with `text.setValue` or `replaceContent`. |
| 147 | + |
| 148 | +## Replacing content |
| 149 | + |
| 150 | +`contentControls.replaceContent` accepts plain text. For richer fragments (paragraphs with formatting, tables, lists), use `doc.insert` to place the content, then `create.contentControl({ at: range, ... })` to wrap the inserted range with a tag. |
| 151 | + |
| 152 | +## Next steps |
| 153 | + |
| 154 | +<CardGroup cols={2}> |
| 155 | + <Card title="Tagged inline text example" icon="text-cursor-input" href="https://github.com/superdoc-dev/superdoc/tree/main/examples/document-api/content-controls/tagged-inline-text"> |
| 156 | + The smallest content-control workflow. `create.contentControl` + `selectByTag` + `text.setValue`. |
| 157 | + </Card> |
| 158 | + <Card title="Contract templates demo" icon="file-text" href="https://github.com/superdoc-dev/superdoc/tree/main/demos/contract-templates"> |
| 159 | + Smart fields and versioned sections composed into one runtime app. |
| 160 | + </Card> |
| 161 | + <Card title="Template Builder" icon="layout-template" href="/solutions/template-builder/introduction"> |
| 162 | + Ready-made React authoring component for content-control templates. |
| 163 | + </Card> |
| 164 | + <Card title="Reference: all operations" icon="code" href="/document-api/reference/content-controls/index"> |
| 165 | + Every `contentControls.*` operation with input, output, and failure codes. |
| 166 | + </Card> |
| 167 | +</CardGroup> |
0 commit comments