Content controls

Discover controls, fill typed values, edit metadata and locks, create text and date controls, and handle XML-bound content.

Content controls mark template content for structured reading and editing. Use Document.contentControls for the main-story collection, or Body.contentControls for a body scope. ContentControl.contentControls provides nested controls; paragraphs provides the control's paragraphs.

Create a runtime with Runtime and setup. Use context and document proxies inside runtime.run().

Find a control and inspect its metadata

ContentControlCollection exposes items, getFirst(), getFirstOrNullObject(), getById(id), getByTag(tag), and getByTitle(title). Tag and title lookup return collections because matches need not be unique. getById() takes a number and returns the first match in document order. The loaded ContentControl.id is string metadata; DOCX IDs can be absent or repeated. Do not treat that value as a globally unique object identity.

This example fills one eligible text control:

await runtime.run(async (context) => {
  const matches = context.document.contentControls.getByTag('customer');
  matches.load('items');
  await context.sync();
  if (matches.items.length !== 1) throw new Error('Expected one customer control');
  const control = matches.items[0]!;
  control.load(['subtype', 'isBound', 'cannotEdit']);
  await context.sync();
  if (control.isBound || control.cannotEdit) return;
  if (control.subtype !== 'plainText' && control.subtype !== 'richText') return;
  control.setValue({ kind: 'text', text: 'Ada' });
  await context.sync();
});

isBound is advisory metadata. Every write checks binding and protection again at sync. A stale read or changed lock can cause the batch to fail. The runtime rejects writes to custom XML-bound controls and does not expose xmlMapping.

Choose the value for the control type

ContentControl.setValue(value) is a DocxEditor extension with a discriminated ContentControlValue union:

ValueUse
{ kind: 'text', text: 'Ada' }Eligible text control
{ kind: 'listItem', value: 'approved' }A declared list item accepted by the control
{ kind: 'checkbox', checked: true }Checkbox state and its displayed glyph
{ kind: 'date', iso: '2026-09-13' }Date value; also accepts a full ISO-8601 instant

Use the loaded subtype to choose an operation. It uses DOCX terms such as plainText, dropDownList, and checkbox. A control's text is not always its semantic value. Writing the text true does not set a checkbox's state. The runtime rejects unsupported values or control structures.

ContentControl.insertText() supports text-like controls. Date, checkbox, and other typed controls refuse with NotSupported. Use the editor's calendar or the documented setValue() extension to set a date.

insertText(text, location) accepts Replace, Start, or End and returns a Range. Replacement uses the control's value path, including placeholder and temporary-wrapper handling. Sync before using that returned range. text, placeholderShown, and temporary are loaded read-only properties.

For an existing placeholder, prefer replacing the control value. For untracked range edits, a range covering the complete prompt can also replace it. The runtime returns NotSupported if a replacement would consume prompt text outside its target or an insertion starts away from the prompt start. The first insertion at the prompt start consumes the placeholder text.

Edit metadata and locks

PropertyAccess
id, subtype, isBoundLoaded read-only metadata
tag, titleLoaded reads and string writes
cannotDelete, cannotEditLoaded reads and boolean writes
text, placeholderShown, temporaryLoaded reads

The runtime combines lock assignments on the same proxy. Changing one lock preserves the other without loading it first. Effective ancestor locks and binding checks still apply; lock setters do not bypass protection. ContentControlLockState exports unlocked, sdtLocked, contentLocked, and sdtContentLocked as vocabulary. There is no public combined lock property taking that alias.

Create a text or date control

Range.insertContentControl(type?) supports RichText, PlainText, and DatePicker; the default is RichText. The range must be an eligible single-paragraph span. Sync creation before setting metadata:

await runtime.run(async (context) => {
  const matches = context.document.body.search('{{customer}}');
  matches.load('items');
  await context.sync();
  if (matches.items.length !== 1) throw new Error('Choose one placeholder');
  const control = matches.items[0]!.insertContentControl('PlainText');
  await context.sync();
  control.tag = 'customer';
  control.title = 'Customer name';
  await context.sync();
});

Use DatePicker for calendar dates such as an effective date. Creation preserves the selected text. The editor opens a calendar for this control. Typed date-format configuration remains unsupported.

ContentControlType includes other Word values, but the runtime rejects those creation modes. The exported ContentControlSubtype vocabulary does not expand insertContentControl() support. Picture controls, repeating-section editing, and typed Office.js subtype objects are unavailable. Use a prepared template for existing eligible list and checkbox controls.

Suggest a control

TrackMineOnly can wrap nonempty ordinary text in PlainText, RichText, or DatePicker controls outside collaboration. Accept keeps the control. Reject restores the original formatted text. The author can set the pending control's tag and title. Empty ranges, existing review markup, and other control structure changes refuse.

Set context.document.changeTrackingMode = 'TrackMineOnly' before insertContentControl() in the creation sample. Keep the same insertContentControl() call and sync boundaries. Creation saves a native deletion and insertion pair. One review decision resolves both halves.

Address or remove control content

getRange(location?) accepts Whole, Content, Start, End, Before, and After. Resolve the returned range before dependent use. delete(true) removes the wrapper and preserves its content. delete(false) removes both wrapper and content. Deletion and value changes can fail because of protection, XML binding, unsupported structure, or tracking mode.

Do not delete a binding or wrapper to force a failed template write. Read again and report an ineligible control to the application or template author.

Next steps

See Text and ranges, Batching, loading, and errors, and Office.js compatibility.