# Fill a DOCX template

> Update every content-control occurrence that represents an application field, then export the DOCX.



Treat your application data as the source of truth. Find controls by tag, update every returned target, and inspect each
mutation receipt before reporting success.

## Try the workflow [#try-the-workflow]

Change the client name. All three occurrences update. Toggle auto-renew to change the Word checkbox stored in the DOCX.

> **Interactive editor: Fill the template**
>
> The application form updates real Word content controls in the service-agreement DOCX.
>
> - Client legal name (`client.legalName`): one form value updates 3 document occurrences.
> - Auto-renew (`agreement.autoRenew`): the checkbox updates the Word checkbox control.
>
> The demo reports how many matching controls changed. Reset restores the prepared template, and Export DOCX downloads the filled document.


## Define the field map [#define-the-field-map]

Keep business-field keys in application code. A tag connects one field to one or more controls in the document:

```ts
export const templateFields = [
  {
    key: 'clientLegalName',
    label: 'Client legal name',
    tag: 'client.legalName',
    type: 'text',
  },
  {
    key: 'clientAddress',
    label: 'Client address',
    tag: 'client.address',
    type: 'text',
  },
  {
    key: 'effectiveDate',
    label: 'Effective date',
    tag: 'agreement.effectiveDate',
    type: 'text',
  },
  {
    key: 'autoRenew',
    label: 'Auto-renew',
    tag: 'agreement.autoRenew',
    type: 'checkbox',
  },
] as const;

export type TemplateField = (typeof templateFields)[number];
export type TemplateFieldKey = TemplateField['key'];

type TaggedContentControl = {
  readonly controlType: string;
  readonly properties: { readonly tag?: string };
};

export function hasCompatibleTemplateFields(items: readonly TaggedContentControl[]) {
  return templateFields.every((field) =>
    items.some((item) => item.properties.tag === field.tag && item.controlType === field.type),
  );
}

```

## Update every occurrence [#update-every-occurrence]

`selectByTag()` returns each matching control. Update them one at a time so the result can distinguish complete,
partial, and failed updates:

```ts
import type { BrowserDocumentApi, ContentControlInfo } from 'superdoc/ui';

export type FieldUpdateResult = {
  failures: string[];
  matched: number;
  unchanged: number;
  updated: number;
};

type ControlType = 'checkbox' | 'text';

async function updateControls(
  doc: BrowserDocumentApi,
  tag: string,
  expectedType: ControlType,
  mutate: (control: ContentControlInfo) => Promise<{ success: boolean; failure?: { code?: string; message?: string } }>,
): Promise<FieldUpdateResult> {
  let items: readonly ContentControlInfo[];
  try {
    ({ items } = await doc.contentControls.selectByTag({ tag }));
  } catch (error) {
    return {
      failures: [error instanceof Error ? error.message : `Could not find controls for ${tag}.`],
      matched: 0,
      unchanged: 0,
      updated: 0,
    };
  }
  const result: FieldUpdateResult = { failures: [], matched: items.length, unchanged: 0, updated: 0 };

  for (const control of items) {
    if (control.controlType !== expectedType) {
      result.failures.push(`${control.id} is ${control.controlType}, not ${expectedType}.`);
      continue;
    }

    try {
      const receipt = await mutate(control);
      if (receipt.success) result.updated += 1;
      else if (receipt.failure?.code === 'NO_OP') result.unchanged += 1;
      else result.failures.push(receipt.failure?.message ?? `Could not update ${control.id}.`);
    } catch (error) {
      result.failures.push(error instanceof Error ? error.message : `Could not update ${control.id}.`);
    }
  }

  return result;
}

export function updateTextField(doc: BrowserDocumentApi, tag: string, value: string) {
  return updateControls(doc, tag, 'text', (control) =>
    Promise.resolve(doc.contentControls.text.setValue({ target: control.target, value })),
  );
}

export function updateCheckboxField(doc: BrowserDocumentApi, tag: string, checked: boolean) {
  return updateControls(doc, tag, 'checkbox', (control) =>
    Promise.resolve(doc.contentControls.checkbox.setState({ target: control.target, checked })),
  );
}

export function didUpdateEveryMatch(result: { failures: readonly string[]; matched: number }) {
  return result.matched > 0 && result.failures.length === 0;
}

export function describeUpdate(result: FieldUpdateResult) {
  if (result.failures.length > 0 && result.matched === 0) return 'The document could not be updated.';
  if (result.matched === 0) return 'No matching controls.';
  if (result.failures.length > 0) return `Updated ${result.updated} of ${result.matched} locations.`;
  if (result.updated === 0) return `${result.matched} locations already match.`;
  return `Updated ${result.updated} ${result.updated === 1 ? 'location' : 'locations'}.`;
}

```

Use the same lookup and receipt pattern for other supported field types. Check `controlType`, call the matching typed
operation, and use the returned receipt to decide what the application reports.

The [content-controls example](https://go.superdoc.dev/examples/content-controls?workflow=fill) contains the complete
Vanilla TypeScript workflow. It also focuses repeated occurrences, exports the DOCX, and verifies the result after
reopening it.

Continue with [Replace clauses from your application](/editor/content-controls/replace-clauses-from-your-application)
for block-level content. Use [custom content-control UI](/editor/custom-ui/content-controls) when your application needs
a persistent field list or richer navigation.
