This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/web/headless/note.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Headless note annotations | Nutrient Web SDK

A NoteAnnotation is the PDF specification’s sticky note — a small icon on the page that reveals a popup of text when clicked. The default note tool drops a note wherever the user clicks. Build your own note flow when you need a different placement strategy, a custom icon set, or notes that something other than a click creates.

Looking for the visual side of this? See the annotations.note slot for replacing the popup that appears when a note annotation is clicked. This page covers the programmatic creation surface. The notes panel guide covers programmatic open/close of the popup itself.

Note annotations vs. annotation notes

These two concepts are easy to confuse:

  • Note annotations (NoteAnnotation) are sticky notes; they’re an annotation type with their own icon and position on the page.
  • Annotation notes are textual notes attached to other annotation types (rectangles, ellipses, ink, etc.). See the annotation notes guide for that case.

This page covers the first item: creating sticky note annotations from code.

When to use this

Reach for headless note annotations when your workflow involves:

  • Bulk-importing comments from a server-side review system as sticky notes at known coordinates.
  • A custom workflow that places a note in response to a search result, a backend event, or an analytical pass.
  • A keyboard shortcut your application defines for “add a note here” with a specific icon.
  • A multiuser review experience where notes appear at coordinates derived from a remote collaboration layer.

API reference

SymbolNotes
NutrientViewer.Annotations.NoteAnnotationThe annotation class for sticky notes.
NutrientViewer.NoteIconEnum of supported icon names, COMMENT, RIGHT_POINTER, RIGHT_ARROW, CHECK, CIRCLE, CROSS, INSERT, NEW_PARAGRAPH, NOTE, PARAGRAPH, HELP, STAR, KEY.

The text property on NoteAnnotation holds the popup text content. It’s required, and you must pass it as { format: 'plain', value: string }. There’s no plain-string shorthand.

Example: Place a note at a click position

Wire a custom toolbar button to enter “place note” mode and drop a note at the user’s next click on the page:

const instance = await NutrientViewer.load({
container: "#viewer",
document: "contract.pdf"
});
const placeNoteButton = document.getElementById("place-note");
// The viewer's root element: the shadow host by default, or the frame's root
// element with the deprecated `useIframe` option.
const viewerRoot =
instance.contentDocument.host ?? instance.contentDocument.documentElement;
const noteSize = 24;
// Where the last press inside the viewer started, so a drag or a scroll
// doesn't place a note.
let pressStart = null;
instance.contentDocument.addEventListener(
"pointerdown",
(event) => {
pressStart = { x: event.clientX, y: event.clientY };
},
{ capture: true, passive: true }
);
instance.contentDocument.addEventListener(
"touchstart",
(event) => {
// A second finger makes it a gesture, not a tap.
pressStart =
event.touches.length > 1
? { x: Infinity, y: Infinity }
: { x: event.touches[0].clientX, y: event.touches[0].clientY };
},
{ capture: true, passive: true }
);
placeNoteButton.onclick = () => {
enterPlaceNoteMode();
};
function enterPlaceNoteMode() {
// Show a crosshair while "place note" mode is on.
viewerRoot.style.cursor = "crosshair";
// Remove first so repeated button clicks don't register the handler twice.
instance.removeEventListener("page.press", placeNote);
instance.addEventListener("page.press", placeNote);
}
async function placeNote(event) {
// Ignore right and middle clicks. Touch taps report button 0 or none.
if (event.nativeEvent.button > 0) return;
// Ignore presses that end away from where they started, such as a text
// selection or a scroll on a touch screen.
const end = event.nativeEvent.changedTouches?.[0] ?? event.nativeEvent;
if (
!pressStart ||
Math.hypot(end.clientX - pressStart.x, end.clientY - pressStart.y) > 10
) {
return;
}
// Place one note per button click. Remove the listener after this press is
// dispatched, so other `page.press` listeners still receive it.
queueMicrotask(() => instance.removeEventListener("page.press", placeNote));
viewerRoot.style.cursor = "";
const note = new NutrientViewer.Annotations.NoteAnnotation({
pageIndex: event.pageIndex,
text: { format: "plain", value: "New note" },
icon: NutrientViewer.NoteIcon.COMMENT,
// `event.point` is in PDF page coordinates. Center the icon on it.
boundingBox: new NutrientViewer.Geometry.Rect({
left: event.point.x - noteSize / 2,
top: event.point.y - noteSize / 2,
width: noteSize,
height: noteSize
})
});
try {
await instance.create(note);
} catch (error) {
console.error("Failed to create the note:", error);
}
}

The page.press event reports the pressed page and the point in PDF page coordinates, so the note lands where the user clicked. The event doesn’t fire when the click selects an annotation, such as another note, or follows a link. For more information, see convert a click to page coordinates.

Example: Place a note with a specific icon

The icon property accepts any value from NutrientViewer.NoteIcon. Pick the icon that matches the intent of the note:

const note = new NutrientViewer.Annotations.NoteAnnotation({
pageIndex: 0,
text: { format: "plain", value: "Help text, read this before signing." },
icon: NutrientViewer.NoteIcon.HELP,
color: NutrientViewer.Color.fromHex("#FFC107"),
boundingBox: new NutrientViewer.Geometry.Rect({
left: 100,
top: 100,
width: 24,
height: 24
})
});
await instance.create(note);

Example: Bulk-create notes from server data

When the notes come from a backend review system, build the annotations from the payload and create them in a single call:

async function importReviewNotes(reviewData) {
const notes = reviewData.map((entry) =>
new NutrientViewer.Annotations.NoteAnnotation({
pageIndex: entry.page,
text: { format: "plain", value: entry.comment },
icon: NutrientViewer.NoteIcon.COMMENT,
creatorName: entry.author,
boundingBox: new NutrientViewer.Geometry.Rect({
left: entry.x,
top: entry.y,
width: 24,
height: 24
})
})
);
await instance.create(notes);
}
  • Notes panel — Programmatically opening and closing the notes panel
  • Annotation notes — The notes attached to other annotation types (different concept)
  • Comments and replies — Nutrient’s collaborative comment system, which is different from sticky notes