Headless note annotations
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
| Symbol | Notes |
|---|---|
NutrientViewer.Annotations.NoteAnnotation | The annotation class for sticky notes. |
NutrientViewer.NoteIcon | Enum 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);}Related
- 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