Project HTML and Markdown

Choose a review view, scope document content, inspect fidelity, and map serialized output back to document targets.

Use projectHtml() or projectMarkdown() when an application needs more than a display string. These asynchronous reads return serialized content together with the document revision, diagnostics, block ranges, annotation status, and an optional output-to-source map.

Use doc.capabilities.check({ operation: 'projectHtml' | 'projectMarkdown', input }) when a workflow needs the same structured support envelope used for rich writes. The check runs the detailed projector and returns its complete output in projection; it does not ask you to repeat a revision-sensitive read. Direct detailed projections and support-check projections both include the common fidelity outcome alongside status, lossy, and all diagnostics.

Use getHtml() or getMarkdown() when only the compact string is needed. The compact methods use the same V2 projection rules but do not expose diagnostics or provenance metadata. Migrating is additive:

const html = doc.getHtml({});

const projection = await doc.projectHtml({
  reviewMode: 'final',
  includeSourceMap: true,
});
const detailedHtml = projection.content;

In the browser facade, await both forms because browser reads may cross the worker boundary. The headless DocumentApi keeps the compact getters synchronous for compatibility; the detailed methods are asynchronous in every host.

Choose the review view

reviewMode controls which side of open tracked changes becomes content:

ModeContent
finalIncludes insertions and destinations; excludes deletions and move sources
originalIncludes deletions and move sources; excludes insertions and destinations
redlineIncludes representable before and after sides with semantic change carriers and annotation metadata

The rule applies to inline text and structural revisions such as paragraphs, list items, table rows and cells, and section boundaries. Formatting-only changes emit one content copy in redline with a semantic carrier. Moves share one logical change ID and identify source and destination sides separately.

HTML uses semantic elements such as <ins> and <del> where the HTML content model allows them. Revised table and list nodes carry change attributes when a wrapper would be invalid. Markdown uses the documented CommonMark/GFM subset and raw semantic HTML when Markdown syntax cannot represent a structural revision without ambiguity.

Choose the story and scope

Omitting in addresses the body story. Pass a supported StoryLocator to read a header, footer, footnote, or endnote. Within that story, omit scope for the whole story, pass a public block address for one block, or pass a contiguous SelectionTarget for a range:

const result = await doc.projectMarkdown({
  in: { kind: 'story', storyType: 'footnote', noteId: '2' },
  reviewMode: 'original',
  scope: {
    kind: 'selection',
    start: { kind: 'text', blockId: 'paragraph-a', offset: 4 },
    end: { kind: 'text', blockId: 'paragraph-b', offset: 12 },
    coordinateSpace: 'tracked',
  },
  includeSourceMap: true,
});

Text offsets are UTF-16 code units. A visible-coordinate selection counts the chosen rendered review view. A tracked-coordinate selection also addresses deletion-side text. The resolved scope in the result always records the canonical tracked target used for the projection.

Range projection fails closed when a boundary would cut through a table, field, or another structure whose partial serialization would be misleading. Invalid or missing targets remain typed input/address errors; the projection does not clip them into a different range.

Inspect status and fidelity

Check outcome, status, lossy, and diagnostics before consuming content. outcome distinguishes preserved, warning-bearing, simplified, and rejected projections using the same vocabulary as rich input checks:

StatusMeaning
successNo lossy diagnostic or fatal error
warningUsable output exists, and at least one diagnostic records a lossy fallback or placeholder
failedA source, scope, or representation condition prevented a truthful projection; output data is empty

Diagnostic codes and structured fields are the stable integration surface. Message prose can change. Each diagnostic names the source construct, disposition, story, applicable public IDs, and an output range when one exists. Diagnostics do not include document excerpts or raw package markup.

The projection contract preserves semantic document content; it is not a DOCX layout renderer or a complete Word-file round trip:

Source constructHTML projectionMarkdown projection
Paragraphs, headings 1-6, baseline marksSemantic HTMLCommonMark/GFM syntax; raw <u> for underline
Lists with exactly resolved Word labelsNested lists with explicit visible-label carriersNative syntax when exact; otherwise raw semantic HTML
Missing or unresolved list labelsReadable [list label unavailable] placeholderThe same raw-HTML placeholder
Rectangular, simple tablesSemantic tableGFM table
Row/column spans or structurally rich tablesSemantic table with spansRaw semantic HTML table
Safe links and visible field resultsLink or visible resultLink or visible result
Images with a public resolvable URLImage elementMarkdown image
Package-only media, drawings, OLE, embedded dataReadable deterministic placeholder and warningReadable deterministic placeholder and warning
Math, content controls, unsupported field shapePreserved readable content or diagnosed placeholderPreserved readable content or diagnosed placeholder
Section, page, and column breaksSemantic marker or diagnosed placeholderSemantic marker or diagnosed placeholder

Exact Word numbering is resolved per effective list level, including legal numbering and overrides. HTML emits the visible label explicitly instead of relying on a browser's list counter. Markdown uses native list syntax only when it can reproduce that label and separator; custom, Roman, alphabetic, legal, tab-suffixed, or no-suffix labels can use the documented raw-HTML list carrier. This is a deterministic semantic representation, not a claim of Word visual parity.

Use extract() when the application needs the structured SDM snapshot rather than a serialized review view. Keep the DOCX itself when the workflow requires package parts, layout, macros, embedded objects, or Word-specific semantics that the construct matrix diagnoses or replaces.

Map output back to document content

Every detailed result contains blocks. Block ranges cover the complete serialized carrier and use UTF-16 offsets into content. A block with identity: 'public' includes the ID accepted by block APIs. A structurally emitted table, row, or cell that only had a positional source locator reports identity: 'unavailable' instead of presenting that locator as a stable public ID. Nested block ranges can overlap.

Set includeSourceMap: true for fine-grained citations. Text entries map escaped output payload ranges to public TextTarget values in tracked coordinates. HTML/Markdown delimiters are intentionally unmapped. Synthetic list labels and placeholders have output ranges but no invented text target.

const projection = await doc.projectHtml({
  reviewMode: 'redline',
  includeSourceMap: true,
});

const outputOffset = projection.content.indexOf('termination');
const entry = projection.sourceMap?.entries.find(
  (candidate) =>
    candidate.kind === 'text' && candidate.output.start <= outputOffset && outputOffset < candidate.output.end,
);

if (entry?.kind === 'text') {
  const current = await doc.info({});
  if (current.revision !== projection.evaluatedRevision) {
    throw new Error('Projection map is stale; project the document again.');
  }

  await doc.comments.create(
    { text: 'Review this source passage.', target: entry.source },
    { expectedRevision: projection.evaluatedRevision },
  );
}

sourceMap.outputCoordinateSpace is utf16; sourceMap.sourceCoordinateSpace is tracked. Escaped output remains atomic: for example, the full HTML entity &amp; can map to one source & code unit. Store evaluatedRevision with every saved map. Any mutation makes the map and its block ranges stale, even when a familiar paragraph ID still exists.

Handle comments and tracked-change annotations

annotations is present even when the fine source map is omitted. Each comment or tracked change reports its public ID, public block IDs when available, output ranges, side, and one of these statuses:

Annotation statusMeaning
emittedAll representable anchored content for the chosen view and scope was emitted
partiallyEmittedOnly part of a discontinuous or scope-clipped anchor survived
omittedThe view removed the anchor, or its construct could not be represented truthfully

A comment on inserted content is omitted in original; a comment on deleted content is omitted in final. In redline, both sides are represented when their structure permits it. An omitted annotation has no output range and uses omittedReason: 'reviewMode' or 'unsupported'. Do not guess an anchor by searching nearby rendered text; use its sourceTarget when present or project a view in which the anchor is emitted.

Compatibility notes

V2 accepts the deprecated unflattenLists option on getHtml() for source compatibility but ignores it. Omitted, true, and false all produce canonical nested lists. Detailed projectHtml() does not accept the option.

See the generated projectHtml() and projectMarkdown() references for the exact input and result schemas.

On this page