Templates and fields

Fill a DOCX template

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

Use a prepared template when your application already knows the values to put in a document. This agreement has a client name in three places and an auto-renew checkbox. The fields are already present; your application fills them.

Try the workflow

Expand the Editor and change the client name. All three occurrences update. Then toggle auto-renew to change the checkbox in the document.

Fill the templateOpening template...
Application form

Start with the prepared example

Open the complete Vanilla TypeScript example and choose Fill fields. It includes the template, form, occurrence arrows, Editor setup, and export button. The snippets below are from that project; they are not a separate Editor setup.

In your own integration, keep the Editor from the Quickstart and load a DOCX with content controls, such as the service-agreement template. Obtain superdoc.activeEditor.doc after onReady before calling the field helpers. The Quickstart's original sample does not contain these fields.

Define the field map

The client-name controls share the tag client.legalName. Their IDs differ because each ID identifies one occurrence. Changing a control does not automatically change others with the same tag; the example updates every match.

Keep the tag and expected type together. For example, client.legalName expects a text control and agreement.autoRenew expects a checkbox.

View the example's field map
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

The example's updateTextField() and updateCheckboxField() helpers follow three steps:

  1. Find matching controls with selectByTag().
  2. Check each control's type, then call text.setValue() or checkbox.setState() on its target.
  3. Count updated, unchanged, and failed occurrences from the mutation receipts.
View the typed update helpers
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.

No matches means the template is missing the expected field. A locked or incompatible occurrence can fail after other occurrences have changed: these individual updates are not one atomic transaction. The example reports partial results and blocks export until failed fields are corrected.

Export and check the result

In the standalone example, choose Export DOCX after the updates finish. Reopen the file in SuperDoc or Word and check all three client names and the checkbox. Field tags remain in the DOCX for later updates.

The embedded demo does not save to your server. Use Load and save to persist the exported bytes in your application.

Next, add fields to your own template. For an application-owned field list, see custom content-control UI.

On this page