EmbedPDF

Annotation Plugin

The Annotation Plugin provides a comprehensive framework for adding, editing, and managing annotations within a PDF document. It supports a wide range of common annotation types, including text markups (highlight, underline), free-hand drawings (ink), shapes (squares, circles), and more.

The plugin is built on an extensible “Tool” system, allowing you to define and customize different annotation behaviors and appearances.

Installation

This plugin has several dependencies that must be installed to handle user interactions and manage state. The history plugin is optional but recommended for undo/redo functionality.

npm install @embedpdf/plugin-annotation @embedpdf/plugin-interaction-manager @embedpdf/plugin-selection @embedpdf/plugin-history

Registration

Import AnnotationPluginPackage and its dependencies. It is crucial to register the dependencies before the annotation plugin itself.

import { createPluginRegistration } from '@embedpdf/core' import { EmbedPDF } from '@embedpdf/core/svelte' // ... other imports import { InteractionManagerPluginPackage } from '@embedpdf/plugin-interaction-manager/svelte' import { SelectionPluginPackage } from '@embedpdf/plugin-selection/svelte' import { HistoryPluginPackage } from '@embedpdf/plugin-history/svelte' import { AnnotationPluginPackage } from '@embedpdf/plugin-annotation/svelte' const plugins = [ // ... other essential plugins createPluginRegistration(DocumentManagerPluginPackage, { /* ... */ }), createPluginRegistration(RenderPluginPackage), // Register dependencies first createPluginRegistration(InteractionManagerPluginPackage), createPluginRegistration(SelectionPluginPackage), createPluginRegistration(HistoryPluginPackage), // Register and configure the annotation plugin createPluginRegistration(AnnotationPluginPackage, { // Optional: Set the author name for created annotations annotationAuthor: 'Jane Doe', }), ]

Usage

The plugin works by combining a UI component to render the annotations with a store to manage the annotation tools and state.

1. The <AnnotationLayer /> Component

This component is responsible for rendering all annotations and handling user interactions like creating, selecting, moving, and resizing. It must be placed inside the <Scroller />’s renderPage snippet and wrapped by a <PagePointerProvider> to correctly process pointer events.

Note that documentId is required for all components to link them to the correct document state.

<script lang="ts"> import { PagePointerProvider } from '@embedpdf/plugin-interaction-manager/svelte'; import { AnnotationLayer } from '@embedpdf/plugin-annotation/svelte'; let { documentId }: { documentId: string } = $props(); </script> {#snippet renderPage(page)} <PagePointerProvider {documentId} pageIndex={page.pageIndex}> <RenderLayer {documentId} pageIndex={page.pageIndex} /> <SelectionLayer {documentId} pageIndex={page.pageIndex} /> <AnnotationLayer {documentId} pageIndex={page.pageIndex} /> </PagePointerProvider> {/snippet} <Scroller {documentId} {renderPage} />

2. Building an Annotation Toolbar

The useAnnotation store provides all the necessary methods to control the plugin for a specific document. You can build a toolbar that allows users to select an “active tool” (like a pen or highlighter) and perform actions like deleting a selected annotation.

<script lang="ts"> import { useAnnotation } from '@embedpdf/plugin-annotation/svelte'; let { documentId }: { documentId: string } = $props(); const annotation = useAnnotation(() => documentId); const deleteSelected = () => { const selection = annotation.provides?.getSelectedAnnotation(); if (selection) { annotation.provides?.deleteAnnotation(selection.object.pageIndex, selection.object.id); } }; </script> <div> <button onclick={() => annotation.provides?.setActiveTool('highlight')}>Highlighter</button> <button onclick={() => annotation.provides?.setActiveTool('ink')}>Pen</button> <button onclick={deleteSelected} disabled={!annotation.state.selectedUid}>Delete</button> </div>

3. Adding a Selection Menu

When an annotation is selected, you can display a contextual menu (e.g., for deleting, editing properties). Use the selectionMenu snippet on <AnnotationLayer />. This snippet receives props including the context object, which contains the selected annotation.

<script lang="ts"> import { AnnotationLayer, useAnnotation } from '@embedpdf/plugin-annotation/svelte'; let { documentId }: { documentId: string } = $props(); const annotation = useAnnotation(() => documentId); const handleDelete = (pageIndex: number, id: number) => { annotation.provides?.deleteAnnotation(pageIndex, id); }; </script> <AnnotationLayer {documentId} pageIndex={page.pageIndex}> {#snippet selectionMenuSnippet({ selected, context, menuWrapperProps, rect })} {#if selected} <span style={menuWrapperProps.style} use:menuWrapperProps.action> <div style:position="absolute" style:top="{rect.size.height + 8}px" style:pointer-events="auto" style:cursor="default" > <button onclick={() => handleDelete(context.annotation.object.pageIndex, context.annotation.object.id)}> Delete </button> </div> </span> {/if} {/snippet} </AnnotationLayer>

The menuWrapperProps provides a style string and an action for your wrapper element—the style handles positioning relative to page rotation, while the action handles event propagation to prevent interaction conflicts with underlying layers.

Live Example

Try using the annotation tools below. Select a tool from the toolbar to start creating annotations. Click on an annotation to select it, and use the delete button or the contextual menu to remove it.

Customizing Annotation UI

The <AnnotationLayer /> is fully headless — you can replace every visual element with your own markup. This includes resize handles, vertex handles, the rotation handle, and the selection outline.

In Svelte, custom handles are provided via snippet props on the resizeUI.component, vertexUI.component, and rotationUI.component configuration objects. Each snippet receives positioning styles (as a CSS string), event handlers, and appearance data that you must bind onto your element for interactions to work correctly.

Custom Handle Snippets

Resize Handle

Replace the default circular resize handles with custom markup. The snippet receives style (a CSS string), backgroundColor, and event handler props.

<script lang="ts"> import { AnnotationLayer, type HandleProps, } from '@embedpdf/plugin-annotation/svelte'; </script> {#snippet resizeHandleSnippet({ style, backgroundColor, key: _key, ...rest }: HandleProps)} <div {...rest} style="{style}; background-color: transparent; border: 2px solid {backgroundColor ?? '#475569'}; border-radius: 2px;" /> {/snippet} <AnnotationLayer {documentId} pageIndex={page.pageIndex} resizeUI={{ size: 10, color: '#475569', component: resizeHandleSnippet }}> </AnnotationLayer>

Vertex Handle

Replace vertex handles (used on polyline/polygon annotations) with custom shapes. This example creates diamond-shaped handles by adding a rotate(45deg) transform.

{#snippet vertexHandleSnippet({ style, backgroundColor, key: _key, ...rest }: HandleProps)} <div {...rest} style="{style}; background-color: {backgroundColor ?? '#475569'}; border-radius: 1px; rotate: 45deg;" /> {/snippet}

Rotation Handle

Replace the rotation handle with fully custom markup. The snippet provides additional props for the connector line and icon color.

<script lang="ts"> import { type RotationHandleComponentProps } from '@embedpdf/plugin-annotation/svelte'; </script> {#snippet rotationHandleSnippet({ style, backgroundColor, connectorStyle, showConnector, iconColor, opacity, border: _border, ...rest }: RotationHandleComponentProps)} {#if showConnector && connectorStyle} <div style={connectorStyle}></div> {/if} <div {...rest} style="{style}; background-color: {backgroundColor ?? '#475569'}; border-radius: 999px; display: flex; align-items: center; justify-content: center; box-shadow: 0 1px 3px rgba(0,0,0,0.25); opacity: {opacity};" > <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke={iconColor ?? 'white'} stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> <path d="M1 4v6h6" /> <path d="M23 20v-6h-6" /> <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10M23 14l-4.64 4.36A9 9 0 0 1 3.51 15" /> </svg> </div> {/snippet}

Configuring Outline and Handle Props

Pass the snippet components alongside color, size, and outline configuration:

<AnnotationLayer {documentId} pageIndex={page.pageIndex} resizeUI={{ size: 10, color: '#475569', component: resizeHandleSnippet }} vertexUI={{ size: 10, color: '#475569', component: vertexHandleSnippet }} rotationUI={{ size: 24, color: '#475569', iconColor: 'white', margin: 28, showConnector: true, connectorColor: '#94a3b8', component: rotationHandleSnippet, }} selectionOutline={{ color: '#475569', style: 'solid', width: 1, offset: 2 }} groupSelectionOutline={{ color: '#64748b', style: 'dashed', width: 2, offset: 3 }} > {#snippet selectionMenuSnippet({ selected, context, menuWrapperProps, rect })} <!-- your custom selection menu --> {/snippet} </AnnotationLayer>

Default Annotation Tools

The plugin comes with a set of pre-configured tools. You can activate any of these tools by passing its id to the setActiveTool method.

Tool NameidDescription
HighlighthighlightCreates text highlight annotations.
UnderlineunderlineCreates text underline annotations.
StrikeoutstrikeoutCreates text strikeout annotations.
SquigglysquigglyCreates squiggly text underline annotations.
PeninkFree-hand drawing tool.
Ink HighlighterinkHighlighterFree-hand highlighter with a multiply blend mode.
CirclecircleDraws ellipse annotations.
SquaresquareDraws rectangle annotations.
LinelineDraws straight line annotations.
ArrowlineArrowDraws a straight line with an arrowhead.
PolylinepolylineDraws multi-segment lines.
PolygonpolygonDraws closed, multi-sided shapes.
Free TextfreeTextAdds a text box annotation.
ImagestampAdds an image stamp. Opens a file picker by default.

Listening to Annotation Events

For more advanced integrations, such as saving annotation data to a backend or synchronizing state with an external data store, you can subscribe to annotation lifecycle events using onAnnotationEvent.

Each event provides the annotation object and a committed flag, which indicates whether the change has been saved to the underlying PDF document in the engine.

<script lang="ts"> import { onMount, onDestroy } from 'svelte'; import { useAnnotation } from '@embedpdf/plugin-annotation/svelte'; let { documentId }: { documentId: string } = $props(); const annotation = useAnnotation(() => documentId); let unsubscribe: (() => void) | undefined; onMount(() => { if (!annotation.provides) return; unsubscribe = annotation.provides.onAnnotationEvent((event) => { console.log(`Annotation event: ${event.type}`, { event }); // Example: Save to backend after a change is committed to the engine if (event.type === 'create' && event.committed) { // yourApi.saveAnnotation(event.annotation); } }); }); onDestroy(() => { unsubscribe?.(); }); </script>

API Reference

Configuration (AnnotationPluginConfig)

OptionTypeDescription
annotationAuthorstringSets the author name for all created annotations. Default: 'Guest'
autoCommitbooleanIf true, annotation changes are automatically saved to the engine. Default: true
toolsAnnotationTool[]An array of custom annotation tools to add or override default tools.
colorPresetsstring[]A list of hex color strings to be used in a color picker UI.
deactivateToolAfterCreatebooleanIf true, the active tool is deselected after an annotation is created. Default: false
selectAfterCreatebooleanIf true, a newly created annotation is automatically selected. Default: true

Component: <AnnotationLayer />

The primary component for rendering and interacting with annotations.

PropTypeDescription
documentIdstring(Required) The ID of the document this layer belongs to.
pageIndexnumber(Required) The page index this layer corresponds to.
scalenumberThe zoom scale of the page.
rotationnumberThe rotation of the page.
resizeUIResizeHandleUICustomize resize handle size, color, or provide a custom snippet.
vertexUIVertexHandleUICustomize vertex handle size, color, or provide a custom snippet.
rotationUIRotationHandleUICustomize rotation handle size, color, margin, icon, connector, or provide a custom snippet.
selectionOutlineSelectionOutlineCustomize the outline around selected annotations (color, style, width, offset).
groupSelectionOutlineSelectionOutlineCustomize the outline for group selections. Falls back to selectionOutline.
selectionOutlineColorstringDeprecated. Use selectionOutline.color instead.

ResizeHandleUI / VertexHandleUI

interface ResizeHandleUI { size?: number; // Handle size in px (default: 12) color?: string; // Background color (default: '#007ACC') component?: Snippet<[HandleProps]>; // Custom snippet (overrides default) } // VertexHandleUI has the same shape

RotationHandleUI

interface RotationHandleUI { size?: number; // Handle size in px (default: 32) color?: string; // Background color (default: 'white') iconColor?: string; // Icon stroke color (default: '#007ACC') margin?: number; // Gap from bounding box edge in px (default: 35) showConnector?: boolean; // Show connector line (default: false) connectorColor?: string; // Connector line color (default: '#007ACC') border?: RotationHandleBorder; // Border configuration component?: Snippet<[RotationHandleComponentProps]>; // Custom snippet } interface RotationHandleBorder { color?: string; // Border color (default: '#007ACC') style?: BorderStyle; // Border style (default: 'solid') width?: number; // Border width in px (default: 1) }

SelectionOutline

type BorderStyle = 'solid' | 'dashed' | 'dotted'; interface SelectionOutline { color?: string; // Outline color (default: '#007ACC') style?: BorderStyle; // Default: 'solid' (single) / 'dashed' (group) width?: number; // Outline width in px (default: 1 / 2) offset?: number; // Outline offset in px (default: 1 / 2) }

The HandleProps passed to a custom snippet include a style CSS string and event handlers, which must be spread onto your custom element for interactions to work.

Snippet: selectionMenuSnippet

Render a custom menu when an annotation is selected.

PropTypeDescription
contextobjectThe selection context containing the annotation object.
selectedbooleantrue if an annotation is currently selected.
menuWrapperPropsMenuWrapperPropsAn object containing style: string for positioning and action: Action to attach to your wrapper element via use:menuWrapperProps.action.
rectRectThe bounding box of the selected annotation.

Store: useAnnotation(documentId)

Connects your components to the annotation plugin’s state and methods for a specific document.

Parameters

ParameterTypeDescription
documentId() => stringA getter function that returns the document ID to track.

Returns

PropertyTypeDescription
providesAnnotationScope | nullAn object with methods to control the plugin, or null if not ready.
stateAnnotationStateReactive state object containing properties like activeToolId, selectedUid.

AnnotationScope Methods

A selection of key methods available on the provides object:

MethodDescription
setActiveTool(toolId)Activates an annotation tool (e.g., 'ink', 'highlight'). Pass null to deactivate.
getActiveTool()Returns the currently active AnnotationTool object, or null.
addTool(tool)Registers a new custom AnnotationTool.
createAnnotation(..)Programmatically creates a new annotation on a page.
updateAnnotation(..)Updates the properties of an existing annotation.
deleteAnnotation(..)Deletes an annotation from a page.
selectAnnotation(..)Programmatically selects an annotation.
getSelectedAnnotation()Returns the currently selected TrackedAnnotation object, or null.
importAnnotations(..)Imports an array of annotations into the viewer.
commit()Manually saves all pending annotation changes to the PDF document.
onStateChange(cb)Subscribes to any change in the annotation state.
onAnnotationEvent(cb)Subscribes to events like annotation creation, updates, or deletion.

The AnnotationEvent Object

The object passed to the onAnnotationEvent callback contains details about the lifecycle event. Its shape varies based on the type property.

Event TypePropertiesDescription
'create'annotation, pageIndex, ctx, committedFired when an annotation is created. ctx may contain extra data like ImageData for stamps. committed is true if the change has been saved to the engine.
'update'annotation, pageIndex, patch, committedFired when an annotation is moved, resized, or its properties change. annotation is the original object, and patch contains only the changed properties.
'delete'annotation, pageIndex, committedFired when an annotation is deleted.
'loaded'totalFired once when the initial set of annotations is loaded from the document. total is the number of annotations loaded.
Last updated on February 12, 2026

Need Help?

Join our community for support, discussions, and to contribute to EmbedPDF's development.