Fields and pagination

Insert inert TOC fields, and update PAGE and NUMPAGES fields with measured pagination and registered fonts.

A field stores an instruction and cached result text. Reading a field does not execute its instruction. You can insert and evaluate plain PAGE and NUMPAGES fields without switches. You can also insert an inert TOC field. The runtime does not calculate its entries.

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

Insert a page field

Range.insertField(location, fieldType?, text?, removeFormatting?) returns Field. It accepts Before, After, Start, End, or Replace. Use Page or NumPages, or Empty with the text PAGE or NUMPAGES. Omitting the type also requires supported instruction text. The runtime rejects removeFormatting: true.

Resolve an anchor, insert the field, then update its result in a separate sync:

await runtime.run(async (context) => {
  const paragraph = context.document.paragraphs.getLast();
  paragraph.load('text');
  await context.sync();
  const anchor = paragraph.getRange('End');
  await context.sync();
  const field = anchor.insertField('After', 'NumPages');
  await context.sync();
  field.updateResult(); // Requires configured pagination on a server.
  await context.sync();
});

New fields start with an empty cached result. Insertion does not calculate pagination. Before and Start use the start endpoint; After and End use the end endpoint. Replace requires an eligible span in one paragraph.

FieldType exports the Office.js-compatible field vocabulary. FieldTypeLiteral provides corresponding string literals. The presence of an enum value does not mean its instruction can run. For example, FieldType.page is supported. Creation supports the PAGE, NUMPAGES, and bounded TOC subset.

Read and change instructions

Body.fields and Range.fields return FieldCollection. A range includes fields fully contained within that span. Collections expose items, getFirst(), getLast(), getFirstOrNullObject(), and getLastOrNullObject().

Load each field's code before inspecting its raw instruction:

await runtime.run(async (context) => {
  const fields = context.document.body.fields;
  fields.load('items');
  await context.sync();
  for (const field of fields.items) field.load('code');
  await context.sync();
  console.log(fields.items.map((field) => field.code));
});

Field.code = 'PAGE' or 'NUMPAGES' changes a supported page-field instruction. It preserves the field identity and cached result until explicit evaluation. Supported simple fields and ordinary complex fields with sibling runs can accept code changes. The runtime rejects edits to protected fields, unsupported instructions, and edits made while tracking changes. Field.delete() removes the field and its cached result through the supported deletion path. Deleted field proxies become invalid.

Configure measured server pagination

Server result updates require CreateServerOptions.pagination. ServerPaginationOptions contains a required measurer and optional producer. Build the measurer from actual font resources using the core layout APIs.

The runnable report agent shows the complete font setup and cleanup:

bun examples/editor-api-consumers/report-agent.ts

Run it from the repository root after installing workspace dependencies. The example loads Carlito regular, bold, italic, and bold-italic files. It registers those files explicitly as Calibri resources with sha256FontBytes and createLayoutShaping. It builds createLayoutShapedMeasurer with a resolver that refuses unavailable requested faces. Pass the configured measurer when opening the document:

const runtime = await DocxEditor.createServer(bytes, {
  pagination: { measurer },
});

Use the document's fonts or deliberately selected substitutes. Font substitution can change page breaks and page counts compared with Word. Bullets can request additional fonts, such as Symbol or Courier New. Provide those resources, or use an explicit registered font for custom Unicode bullets.

Dispose the runtime when the job finishes. Release its shaping resources with disposeLayoutShaping(shaping) as the example shows. A fixed-width test measurer does not establish production pagination fidelity. A browser runtime uses its attached editor's layout.

Update results after document edits

Field.updateResult() computes cached text from measured pagination, including supported section page-number formats. The computed result remains in the DOCX after saving and reopening. Load the owning body's text after evaluation when you need the visible result. The Field object exposes code, not a separate result property.

Multiple field result updates can share one sync, including fields in one paragraph. They can share queries, but cannot share other document writes. Finish text, table, picture, font, and page-layout edits first. Then evaluate fields in a separate transaction.

Updates fail with NotSupported if pagination is missing, the instruction is unsupported, or the cached result cannot be rewritten. The server does not guess a page count or automatically resolve external field instructions. On refusal, preserve the document and provide valid measurement resources or select an eligible field.

Insert a table of contents field

Use TOC to save a table of contents instruction for an application that calculates it. Creation does not populate entries. The runtime refuses TOC calculation through updateResult().

This example inserts a field for heading levels 1 through 3:

await runtime.run(async (context) => {
  const anchor = context.document.body.getRange('Start');
  await context.sync();
  anchor.insertField('Before', 'TOC', '\\o "1-3" \\h \\z \\u');
  await context.sync();
});

The runtime accepts \o with ascending levels 1 through 9, plus \h, \z, and \u. It rejects duplicate switches, unknown switches, TOC code writes, and tracked field creation.

Next steps

See Page layout and stories and Batching, loading, and errors.