This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/web/release-notes/1-23.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. 1.23 release notes

1.23 release notes

RSS

Nutrient Web SDK 1.23 makes several save and loading APIs report failures they used to hide. These include Instant changes Document Engine refuses, characters a Content Editor save couldn’t keep, layer operations that never ran, and malformed Instant JSON annotations. It also adds document provenance and page-pair coverage to text comparison and switches JWT collaboration contexts without reloading the viewer. Custom theme objects now require stronger support colors. See the changelog for full details.

Custom theme objects need stronger support colors

The SDK now runs on Baseline UI 3.2.1, which adds a stronger shade to each status color group. A full theme object passed as theme must define stronger in the error, success, warning, and info groups under color.support. Without them, load() fails with an Invalid theme error such as Expected string, received undefined at "color.support.error.stronger":

NutrientViewer.load({
theme: {
// ...the rest of your theme.
color: {
// ...
support: {
error: {
subtler: "#711b00",
subtle: "#8a2100",
medium: "#fe7a68",
strong: "#ffd4ce",
stronger: "#ffebe9"
},
// success, warning, and info each need `stronger` too.
},
},
},
});

The example values come from the high-contrast dark theme, where subtler is the darkest shade and stronger is the lightest. In the light themes, the shades run the other way. The TypeScript type requires the four keys as well. The built-in NutrientViewer.Theme values aren’t affected. The error, success, warning, and info colors also change slightly in the dark and high-contrast dark themes. For the full set of theme tokens, refer to the custom theme guide.

Content Editor saves report the characters they couldn’t keep

A character that no loaded font has a glyph for can’t be written into the PDF. The save still succeeds, but the character is lost. instance.saveContentEditingSession() and ContentEditing.Session.commit() now resolve with a ContentEditing.SaveResult that lists those characters per text block, instead of resolving with void:

const result = await instance.saveContentEditingSession();
if (result.status === "reported") {
for (const { pageIndex, textBlockId, characters } of result.uncoveredCodepoints) {
console.warn(`Page ${pageIndex + 1}, block ${textBlockId}: lost ${characters.join("")}`);
}
}

A reported result with an empty uncoveredCodepoints array means everything was written. reportUnavailable means the document was saved, but its loss report couldn’t be read. Each entry also carries unidentifiedCharacterGroups, the number of character groups lost without identifying the exact characters. To avoid the loss, supply a font that covers the characters through customFonts.

Two compatibility changes: Code that types the returned promises as Promise<void> needs updating, and saveContentEditingSession() now rejects when saving fails instead of resolving successfully. exportContentEditorPDF() is unchanged. For more information, refer to the Content Editor API guide.

Refused Instant changes reject ensureChangesSaved() and save()

In server-backed mode with Instant, Document Engine can refuse a single record change. Examples include renaming a form field to a name another field already uses, or a change the user’s collaboration permissions don’t allow. The SDK used to swallow the refusal. Both methods resolved, and the viewer kept showing the value the server never took. A refused new annotation appeared and then vanished, with nothing telling the caller. Both methods now reject with the server’s reason:

try {
await instance.save();
} catch (error) {
if (error instanceof NutrientViewer.SaveError) {
error.reason.forEach(({ error, object, modificationType }) => {
console.warn(modificationType, object, error.message);
});
}
}

In TypeScript, instanceof NutrientViewer.SaveError doesn’t narrow the type of error, so reading error.reason needs a type assertion.

save() rejects with a SaveError listing every refusal recorded since it was last called, even when nothing is queued. A close guard built on await instance.save() can therefore stop the editor from closing on a lost write. ensureChangesSaved() rejects with an array whose entry for the refused change holds the error. Conflicts and superseded changes are still resolved silently, because the server returns the winning version and nothing is lost. hasUnsavedChanges() is unchanged.

On every backend, ensureChangesSaved() now also rejects when the latest change to an object failed to save. Previously, it confirmed the object as saved. It also no longer waits indefinitely after a failed create. For more information, refer to the Document Engine saving guide.

Layer methods settle with the operation they perform

setLayersVisibilityState() used to resolve before the layer visibility state had been applied. In server-backed mode, it resolved having applied nothing, and the failure only appeared as an unhandled rejection in the console. It now settles once the state is applied and a failure reaches the caller.

The layer methods are standalone mode only. In server-backed mode, getLayersVisibilityState() and setLayersVisibilityState() now reject with an error that names the method and the mode, as createLayer() already did. For more information, refer to the layer visibility guide.

Note popups with custom renderers

Since 1.13.0, a note annotation whose appearance was replaced by a custom renderer with append: false had no UI at all when selected. It had no popup, no annotationTooltipCallback items, and no ui.annotations.note slot. Selecting such a note opens its popup again, as it did in 1.12.0. The note icon stays replaced, and hovering doesn’t open the popup.

If you built your own popup on top of the missing one, hide the SDK’s popup by returning null from the note slot:

NutrientViewer.load({
ui: {
annotations: {
note: (getInstance, id) => ({
render: () => null,
}),
},
},
});

Stricter annotation validation on Instant JSON import

An annotation carrying a malformed bbox, lines, rects, or text.format is now skipped instead of failing with a raw TypeError or importing in a corrupt state. The skip is logged with the field named, such as “Missing bbox field”. Annotations.fromSerializableObject() throws that error. The checks cover:

  • A null bbox, lines, or rects, and a bbox or rects entry that isn’t exactly four numbers.
  • Ink lines whose points have no matching intensities, or a different number of entries per path.
  • A text.format other than xhtml or plain, matched case-insensitively.

A document that imported before with one of these values now drops that annotation. For the format, refer to the Instant JSON guide.

Standalone URL loads confirm the document length

When a standalone document is loaded from a URL, the SDK uses the length the server reports to load the document progressively. A cache or proxy holding an earlier revision can report a superseded length. That made the document load from that earlier revision with missing form fields or outdated values. The reported length is now confirmed with the server before progressive loading starts, and it stays authoritative for the whole download.

A server that won’t confirm the length gets the full document instead of a progressive load, same-origin included. Loads that use range requests make one or two extra small requests, issued alongside the first transfer. A document that stops short of the confirmed length reports an error instead of loading truncated. allowLinearizedLoading keeps its meaning and default. For more information, refer to the linearized downloads guide.

Text comparison provenance and coverage

The text comparison UI shows a header above each compared document. It identifies the document by its file name or PDF title and shows the PDF metadata dates. It also reports whether the comparison finished and how many compared page pairs contain changes. Both parts are enabled by default and can be configured with comparisonHeader:

NutrientViewer.loadTextComparison({
container: "#comparison",
documentA: { source: "/contracts/v1.pdf", fileName: "Contract v1.pdf" },
documentB: "/contracts/v2.pdf",
comparisonHeader: {
documentInformation: true,
comparisonStatus: true,
preferredDocumentIdentifier: "pdfTitle",
},
});

documentA and documentB still accept a URL or an ArrayBuffer. They now also accept an object with a source and an optional fileName to show in the header. preferredDocumentIdentifier defaults to "fileName", and the other identifier is the fallback. Missing or invalid metadata is shown as unavailable.

The result of instance.compareDocuments() now carries pagePairResults, one entry per requested page pair in request order, each with originalPageIndex, changedPageIndex, and hasChanges:

const result = await instance.compareDocuments(comparisonDocuments, operation);
const changedPairs = result.pagePairResults.filter((pair) => pair.hasChanges);

An unmatched page — when the two documents have different page counts — has undefined for the missing index and counts as changed. Page pairs are no longer dropped in that case, or when the same page is compared more than once. For more information, refer to the text comparison guide.

Switching collaboration contexts with setSession()

instance.setSession() can now move a server-backed Instant viewer between JWTs with different collaboration contexts without unloading it. One example is switching a teacher from one student’s submission to the next on the same document:

instance.setSession(jwtForNextStudent);

A JWT whose claims match the current one keeps pending changes queued and resumes syncing them under the new token. It can differ only in exp, nbf, iat, or jti. A JWT that changes the collaboration context, such as user_id, collaboration_permissions, or default_group, discards changes that haven’t reached the server yet. It then reloads the records the new JWT allows in the background. The method doesn’t wait for that reload. setSession() isn’t supported for app-provided documents loaded with DWS Viewer API. For more information, refer to the client authentication guide.

Updated collection deprecations

Nine more collection and model methods are marked deprecated in the published type declarations, each naming a replacement. No method was removed, and every call keeps working. The API reference now shows the same deprecations on Immutable.List, Map, Set, and the other collection types.

MethodReplacement
keySeqUse keys().
valueSeqUse values().
hashCodeUse equals() to compare values.
unionUse merge(), which does the same thing.
subtractUse filter() to keep the values the other collections don’t include.
toStackUse toList().
zip, zipAll, and zipWithSpread both collections into arrays and pair them by index. Each deprecation notice shows the exact code.

Six methods deprecated in 1.22 are supported again, and their deprecation is removed: filterNot, flatten, hasIn, removeIn, toMap, and mergeIn.

Other additions

  • Selecting many annotations at once is faster.
  • Preparing text selection is faster for documents with large character sets, such as CJK documents.
  • Repeated asset validation requests during document loads are avoided.
  • GdPicture is updated to 14.4.9. Refer to the GdPicture changelog for details.

Rendering

  • Fixes page text flickering between sharp and blurry at some Fit to width container widths with the next page renderer.
  • Fixes incorrect or missing text in PDFs that use non-embedded fonts in standalone mode.
  • Fixes annotation appearance rendering errors during document transitions.
  • Fixes flattened comment markers rendering their text in the marker color, which made the text unreadable.
  • Fixes emojis made of several parts, such as family or flag emojis, being split apart in annotations.
  • Fixes document comparison drawing extra lines across drawings that hide the internal edges of filled areas with hairline strokes in the fill color.
  • Fixes applying a redaction painting a black box over text it doesn’t remove, and repeating that box on every page that shares the same graphic.
  • Fixes a redaction of a graphic drawn more than once on the same page removing content at only one of those places.
  • Fixes spaces missing from text copied from certain OCR-processed PDF documents.

Annotations

  • Fixes callout annotations on rotated pages being drawn, selected, and repositioned in the wrong place, with their text outside the box.
  • Fixes callout annotations breaking when editing is canceled with the Escape key.
  • Fixes the border color of a callout annotation not changing after the document is reloaded.
  • Fixes the leg of a callout annotation not redrawing when one of its points is moved after the document is reloaded.
  • Fixes annotation groups not being preserved when grouped annotations are pasted or duplicated.
  • Fixes pasting text into a plain text annotation changing its font color, font, or size.
  • Fixes text markup tools for PDFs that allow annotations but deny text extraction.

Import, export, and flattening

  • Fixes annotation dates being overwritten with the import time when importing XFDF.
  • Fixes importing XFDF files containing lowercase hexadecimal values resulting in corrupted annotation appearances or text.
  • Fixes a stamp annotation losing its text when its exported XFDF or Instant JSON is imported back into the document.
  • Fixes slow XFDF exports for rich text annotations containing emojis.
  • Fixes deeply nested action chains causing a stack overflow when importing or exporting Instant JSON, or when running them.
  • Fixes Chinese, Japanese, and Korean text in annotations disappearing in third-party PDF viewers.
  • Fixes text annotations using standard fonts causing font errors in Adobe Acrobat.
  • Fixes flattening silently deleting an annotation whose appearance couldn’t be generated. The operation now fails instead.
  • Fixes flattened PDF export failing for deeply nested form fields in Safari, and crashing for PDFs with cyclic form field references.

Forms and document editing

  • Fixes a PDF JavaScript exception when a script assigns a Boolean to a form field’s display property, which blocked scripted show and hide logic.
  • Fixes a form field’s JavaScript actions not running when the field’s widget annotation carries an action of its own.
  • Fixes a calculated form field restored through the instantJSON load configuration keeping its stored value instead of the value computed by its calculation script.
  • Fixes undoing or redoing the deletion of a form field failing in Document Engine mode. Deleting one widget of a shared form field now preserves choice field options and keeps the remaining radio button selection correct.
  • Fixes radio buttons that share an export value regenerating their appearance when the selection didn’t change.
  • Fixes regular text in form fields becoming bold after the field loses focus.

Content Editor

  • Fixes characters typed in the Content Editor being dropped on save when the font applied to them doesn’t cover them. Another loaded font that covers the character is now used instead.
  • Fixes text entered in the Content Editor appearing as empty boxes when working with embedded fonts.
  • Fixes Content Editor changes failing to save in documents containing multiple embedded subsets of the same font.
  • Fixes the Content Editor replacing document fonts that permit unrestricted embedding with fallback fonts.
  • Fixes a discarded content editing session’s edits reappearing in the next session.
  • Fixes an uncatchable “Assertion failed” promise rejection when a text block move or page load finishes after its content editing session was saved and replaced.

Digital signatures

  • Fixes earlier digital signatures being marked invalid after signing another field in a tagged PDF.
  • Fixes intermittent failures when preparing a digital signature.
  • Fixes incorrect validation errors for PAdES B-LT signatures with incomplete embedded CRL data. Certificates left unchecked by the embedded revocation data produce a warning and don’t establish trust.
  • Fixes PAdES signatures getting an inconclusive revocation warning when the signature itself archives an OCSP response for a certificate the embedded validation data leaves unchecked.

Collaboration

  • Fixes a failed save in server-backed mode persisting form field values while losing the image and signature annotations created in the same save. Attachments are now uploaded with the first sync request.
  • Fixes ensureChangesSaved() never resolving when autoSaveMode is INTELLIGENT.

User interface and accessibility

  • Fixes keyboard focus escaping the annotation deletion confirmation dialog.
  • Fixes exporting and saving moving keyboard focus out of the text field or annotation being edited.
  • Fixes an extra keyboard tab stop before the first item of the annotations list and the form field list in Firefox.
  • Fixes a selected comment marker being announced to screen readers with an untranslated identifier instead of its localized annotation type.
  • Fixes WheelZoomMode.ALWAYS zooming the document instead of scrolling the measurement scale editor and other dialogs.
  • Fixes the mouse wheel not changing pages in ScrollMode.PER_SPREAD when the pointer is beside the page. The same fix applies over the page itself when the viewer doesn’t start at the left edge of the window.
  • Fixes the viewer jumping back to the first page after importing a document.
  • Fixes custom color preset labels showing the message ID instead of the configured text.

Loading, platform, and API reference

  • Fixes the viewer disappearing when scrolling to new pages as a linearized download finishes.
  • Fixes an uncatchable unhandled promise rejection when the AbortSignal passed to NutrientViewer.load() is aborted while the document is still loading.
  • Fixes memory growth when repeatedly loading and unloading standalone documents with the next page renderer.
  • Fixes loading PDFs with deeply nested page trees in WebKit browsers.
  • Fixes Office-to-PDF conversions not being reported in usage analytics.
  • Fixes repeated console errors when a Content Security Policy blocks usage metrics uploads.

For a full list of fixes, refer to the changelog.

Document Engine 1.5.6 or later can run this release. In server-backed mode, using SearchType.WORD_BASED, removing password protection during PDF export, and removing annotation notes after an XFDF roundtrip require Document Engine 1.16.0 or later. Document-defined annotation tab order, Annotation.createdBy, overprintPreview, and blackRendering require Document Engine 1.18.0 or later. Compound measurements are standalone mode only and are ignored by Document Engine. See the Web SDK and Document Engine compatibility requirements.

For a complete list of changes, bug fixes, and improvements, refer to the changelog. For previous release notes, refer to the Web SDK 1.22 release notes. We appreciate your feedback and contributions as we continue to enhance Nutrient Web SDK.