How to Work with Annotations in TypeScript
This guide shows how to add, flatten, and search PDF annotations with
Aspose.PDF FOSS for TypeScript. The Page class exposes one Add* method
per annotation subtype — markup (highlight, underline, strikeout, squiggly),
shapes (square, circle, line, polygon, ink), text (sticky note, free text),
and links — each returning a typed Annotation handle that can be read back
or flattened later. It requires Node.js 22 or later.
Step-by-Step Guide
Step 1: Install the Package
asposefoss/pdf is not yet published — build from source until it ships. See the project README for build instructions.
Verify the installation by importing the Document class in a new
TypeScript file — this line should resolve without error once the package
is installed:
import { Document } from '@asposefoss/pdf';Step 2: Import Required Classes
Import Document to open the file and Page for the annotation type
signatures used in the steps below:
import { Document, Page } from '@asposefoss/pdf';Step 3: Add Markup Annotations
Page.AddHighlight(), Page.AddUnderline(), Page.AddSquiggly(), and
Page.AddStrikeOut() all take a quads array — one 8-number quad
(x1,y1,x2,y2,x3,y3,x4,y4) per highlighted text region — plus a color
and optional contents. The library generates the appearance stream (/AP)
for all four subtypes automatically:
import { Document } from '@asposefoss/pdf';
const doc = Document.OpenFile('input.pdf');
const page = doc.Pages[0];
page.AddHighlight({
quads: [72, 700, 300, 700, 72, 685, 300, 685],
color: [1, 1, 0],
contents: 'Yellow highlight',
});
page.AddUnderline({
quads: [72, 660, 300, 660, 72, 645, 300, 645],
color: [0, 0, 1],
});
page.AddStrikeOut({
quads: [72, 620, 300, 620, 72, 605, 300, 605],
color: [1, 0, 0],
});
doc.WriteTo('annotated.pdf');Step 4: Add Shape and Ink Annotations
Page.AddSquare() and Page.AddCircle() take a bounding rect plus
color (stroke) and optional fill. Page.AddLine() takes a 4-number
line (x1,y1,x2,y2) and optional arrow endings. Page.AddInk() takes
paths — an array of flat point-pair arrays, one per pen stroke:
page.AddSquare({ rect: [100, 500, 220, 560], color: [0.8, 0, 0], fill: [1, 1, 0.5], width: 2 });
page.AddCircle({ rect: [250, 500, 370, 560], color: [0, 0.5, 0], width: 2 });
page.AddLine({
line: [100, 470, 370, 470],
color: [0, 0, 0.7],
width: 2,
startEnding: 'OpenArrow',
endEnding: 'ClosedArrow',
});
page.AddInk({
paths: [[100, 400, 130, 430, 160, 390, 190, 420]],
color: [0.6, 0, 0.6],
width: 2,
});Step 5: Add a Sticky Note and Free Text
Page.AddTextNote() places a clickable icon that opens a comment popup.
Page.AddFreeText() draws text directly on the page inside its rect:
page.AddTextNote({
rect: [400, 700, 420, 720],
icon: 'Note',
author: 'Reviewer',
contents: 'This is a sticky-note annotation.',
});
page.AddFreeText({
rect: [400, 600, 550, 660],
contents: 'FreeText sample',
fontSize: 10,
align: 'center',
fill: [1, 1, 0.8],
width: 1,
});Step 6: Flatten an Annotation into Static Content
Every annotation handle returned by an Add* call has a Flatten() method
that bakes the annotation’s appearance into the page’s content stream and
removes it from /Annots. After flattening there is nothing left to click
or edit in a viewer:
const note = page.AddFreeText({
rect: [210, 535, 470, 590],
contents: 'Sticky note — flattened into the page.',
fontSize: 11,
align: 'left',
});
note.Flatten(); // -> boolean; bakes the annotation, unwires itself from /Annots
Step 7: Search Annotation Text
Page.SearchAnnotationText() and Page.SearchAnnotations() are disjoint
in both directions: SearchAnnotationText() matches literal annotation
metadata (/Contents, /T, /Subj), while SearchAnnotations() matches
the rendered text of markup annotations such as FreeText:
const metadataHits = page.SearchAnnotationText('confidential');
for (const hit of metadataHits) {
console.log(hit.key, hit.value); // e.g. 'Contents', 'confidential'
}
const renderedHits = page.SearchAnnotations('confidential');
console.log(renderedHits.length);Common Issues and Fixes
Page.AddHighlight() (or another markup method) draws in the wrong
place. quads is an 8-number flat array (x1,y1,x2,y2,x3,y3,x4,y4), not
a rect — passing a 4-number rectangle produces a degenerate or missing
quad. Build one quad per line of highlighted text.
A flattened annotation is still clickable. Flatten() must be called
on the object returned by the Add* call, and the document must be saved
(doc.WriteTo() / doc.Save()) after flattening — the change only exists
in memory until the file is written back out.
SearchAnnotationText() returns no hits even though the text is
visible on the page. It only searches annotation metadata fields
(/Contents, /T, /Subj) — visible markup text rendered from an
annotation’s own appearance (e.g. a FreeText body) is found by
SearchAnnotations() instead, and neither searches ordinary page content
(use Page.GetText() or Page.Search() for that).
Redaction marks do not show up when searching annotations.
Page.AddRedact() creates a RedactAnnotation, a distinct subtype from
the markup/shape/text annotations covered here — see the
Redaction
guide.
Frequently Asked Questions
How many annotation subtypes does Page support?
The library generates appearances for markup (highlight, underline, strikeout, squiggly), shapes (square, circle, line, polygon, ink), text (sticky note, free text), links, and stamps — plus file attachment and redaction annotations, which are covered in their own how-to guides.
Can I read annotations that already exist on a page?
Yes — Page.Annotations returns the existing annotations as an array of
typed handles you can inspect, edit, or flatten, and
Page.RemoveAnnotation(a) removes one.
What is the difference between flattening an annotation and applying a redaction?
Flatten() bakes an annotation’s appearance into the page and removes
the annotation object, but any text or image content underneath it is
untouched. ApplyRedactions() destructively rewrites the content stream
so the covered content itself is gone — see the Redaction guide for
details.
Are annotations preserved through a save/open round trip?
Yes — annotations added before doc.WriteTo() / doc.Save() are written
into the PDF and read back correctly on the next Document.Open() /
Document.OpenFile().