Adding PDF annotations programmatically enables teams to automate document review workflows, build collaboration tools, and implement markup systems. Whether you’re building document approval systems, implementing automated feedback tools, or creating document annotation platforms, the annotations API provides complete control over annotation types, enabling you to add sticky notes, text markup, stamps, and links, and manage existing annotations without manual PDF editing.

How Nutrient helps you achieve this

Nutrient Python SDK handles PDF annotation structures and appearance stream generation. With the SDK, you don’t need to worry about:

  • Parsing annotation dictionaries and appearance streams
  • Managing annotation flags and border styles
  • Handling coordinate transformations and bounding box calculations
  • Complex annotation state management and reply chains

Instead, Nutrient provides an API that handles all the complexity behind the scenes, letting you focus on your business logic.

Complete implementation

Below is a complete working example that demonstrates adding various annotation types to a PDF. The following lines set up the Python application. The import statements bring in all necessary classes from the Nutrient SDK:

from nutrient_sdk import Document
from nutrient_sdk import PdfEditor
from nutrient_sdk import Color
from nutrient_sdk import NutrientException

The main() function defines the entry point that will contain the annotation creation logic. The Document.open() call opens the PDF document. The context manager(opens in a new tab) syntax ensures the document is automatically closed when you’re done, preventing resource leaks. The following code creates a PDF editor, accesses the page collection, and ensures at least one page exists by adding a letter-size page (612×792 points) if the document is empty:

def main():
try:
with Document.open("input.pdf") as document:
editor = PdfEditor.edit(document)
pages = editor.get_page_collection()
if pages.get_count() == 0:
pages.add(612.0, 792.0)
page = pages.get_first()
annotations = page.get_annotation_collection()

The following code adds a sticky note annotation at coordinates (100, 700) with author metadata, subject, and comment text. After creation, the color is set to yellow using ARGB values (alpha, red, green, blue). Sticky notes are useful for adding comments and review feedback to documents:

sticky_note = annotations.add_sticky_note(
100.0, 700.0,
"Test Author",
"Test Subject",
"This is a sticky note comment"
)
# Customize the color
sticky_note.color = Color.from_argb(255, 255, 255, 0)

The following code blocks add four text markup annotation types: highlight, underline, strikeout, and squiggly. Each annotation is positioned using x, y, width, and height coordinates to define the markup area. The methods accept author and contents parameters for metadata. Colors are customized after creation using ARGB values. These annotations are commonly used for document review workflows to mark text for emphasis, correction, or deletion:

highlight = annotations.add_highlight(
50.0, 600.0, 150.0, 20.0, # x, y, width, height
"Highlighter",
"Highlighted text area"
)
highlight.color = Color.from_argb(128, 255, 255, 0)
underline = annotations.add_underline(
50.0, 550.0, 150.0, 20.0, # x, y, width, height
"Underliner",
"Underlined text area"
)
underline.color = Color.from_argb(255, 0, 0, 255)
strike_out = annotations.add_strike_out(
50.0, 500.0, 150.0, 20.0, # x, y, width, height
"Striker",
"Struck out text area"
)
strike_out.color = Color.from_argb(255, 255, 0, 0)
squiggly = annotations.add_squiggly(
50.0, 450.0, 150.0, 20.0, # x, y, width, height
"Squiggler",
"Squiggly underlined area"
)
squiggly.color = Color.from_argb(255, 0, 128, 0)

The following code adds a rubber stamp annotation at coordinates (300, 600) with dimensions 100×50 points. Stamps are created with a default “Draft” style and can be customized with title and contents text. The color is set to purple using ARGB values. Stamp annotations are typically used for approval workflows to indicate document status:

stamp = annotations.add_stamp(
300.0, 600.0, 100.0, 50.0, # x, y, width, height
"Stamp Title",
"Stamp contents"
)
# Customize the color
stamp.color = Color.from_argb(255, 128, 0, 128)

The following code adds a new page to the document and creates an internal link annotation at coordinates (300, 500) with dimensions 200×20 points. The set_destination() method configures the link to navigate to page 2 at coordinates (0, 792). Link annotations enable navigation within documents and to external resources:

pages.add(612.0, 792.0)
page_link = annotations.add_link(300.0, 500.0, 200.0, 20.0)
page_link.set_destination(2, 0.0, 792.0)

The annotation collection supports iteration using a for loop, enabling you to examine or process all annotations on a page. The type(annot).__name__ expression retrieves the annotation type for logging or filtering purposes. The remove_at() method removes an annotation by its zero-based index position. The following code iterates through all annotations, prints their types, removes the first annotation, saves the changes, and closes the editor. The try-except block handles potential errors using NutrientException:

for annot in annotations:
print(f"Annotation: {type(annot).__name__}")
annotations.remove_at(0)
editor.save_as("output.pdf")
editor.close()
except NutrientException as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()

Conclusion

The annotation creation workflow consists of several key operations:

  1. Open the document and create an editor.
  2. Access the page collection and ensure at least one page exists.
  3. Retrieve the annotation collection for the target page.
  4. Add sticky note annotations with author and comment metadata.
  5. Add text markup annotations (highlight, underline, strikeout, squiggly) with positioning and colors.
  6. Add stamp annotations for document status indication.
  7. Add link annotations for internal page navigation.
  8. Iterate through annotations for processing or filtering.
  9. Remove annotations by index position.
  10. Save and close the editor.

Nutrient handles annotation dictionary structures and appearance stream generation so you don’t need to understand PDF annotation specifications or manage coordinate transformations manually.