Skip to main content

Annotation Editing & eSign

Let your users draw, highlight, add text, stamp images, and sign documents directly in the viewer — then persist their work to your server and restore it later. The annotation editor is the bundled PDF.js editor; ng2-pdfjs-viewer exposes it through Angular inputs, outputs, and promise-based methods.

Editor Modes​

The active editing tool is controlled with the two-way bindable annotationEditor input:

import { AnnotationEditorMode } from 'ng2-pdfjs-viewer';

export class MyComponent {
// 'disable' | 'none' | 'freetext' | 'highlight' | 'stamp' | 'ink' | 'signature' | 'comment'
editorMode: AnnotationEditorMode = 'none';

startDrawing() {
this.editorMode = 'ink';
}
}
<ng2-pdfjs-viewer
pdfSrc="assets/sample.pdf"
[(annotationEditor)]="editorMode">
</ng2-pdfjs-viewer>

<button (click)="startDrawing()">Draw</button>

The binding is two-way: when the user clicks an editor button in the viewer toolbar, annotationEditorChange fires and your property updates. Setting the property programmatically activates that tool in the viewer.

Mode values:

  • none — no tool active, but the editor toolbar buttons stay visible
  • disable — tears the editing UI down entirely
  • freetext — text boxes
  • highlight — text highlighting
  • stamp — image stamps
  • ink — freehand drawing
  • signature — signature placement (requires enableSignatureEditor)
  • comment — threaded comments (requires enableCommentEditor)

Editing requires a loaded document; mode changes made before the document is ready are queued and applied automatically.

Toolbar Visibility​

The annotation editor buttons in the toolbar are shown by default. Hide them with showAnnotations:

<ng2-pdfjs-viewer
pdfSrc="assets/sample.pdf"
[showAnnotations]="false">
</ng2-pdfjs-viewer>

Highlight Color Presets​

Customize the highlight editor's color palette with highlightEditorColors, using the PDF.js name=#hex list format. It is applied before the document opens:

<ng2-pdfjs-viewer
pdfSrc="assets/sample.pdf"
highlightEditorColors="yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F">
</ng2-pdfjs-viewer>

Opt-in Editors: Signature & Comment​

Two editors are opt-in via boolean inputs:

<ng2-pdfjs-viewer
pdfSrc="assets/contract.pdf"
[enableSignatureEditor]="true"
[enableCommentEditor]="true">
</ng2-pdfjs-viewer>
  • enableSignatureEditor (default false) — the PDF.js signature editor: users can draw a signature, type one, or upload an image.
  • enableCommentEditor (default false) — threaded comment popups on highlights, with edit/delete and undo/redo.

Both are init-time options: they ride the viewer URL and are read before the postMessage channel exists, so changing them after the document has loaded reloads the viewer.

Editor State Events​

(onAnnotationEditorStateChange) emits an AnnotationEditorState whenever the editor's editing/undo state changes — ideal for driving "unsaved changes" and save-button UX:

interface AnnotationEditorState {
isEditing: boolean;
isEmpty: boolean;
hasSomethingToUndo: boolean;
hasSomethingToRedo: boolean;
hasSelectedEditor: boolean;
}
import { AnnotationEditorState } from 'ng2-pdfjs-viewer';

export class MyComponent {
canSave = false;

onEditorState(state: AnnotationEditorState) {
// Enable the save button once there is something to save
this.canSave = !state.isEmpty || state.hasSomethingToUndo;
}
}
<ng2-pdfjs-viewer
pdfSrc="assets/sample.pdf"
(onAnnotationEditorStateChange)="onEditorState($event)">
</ng2-pdfjs-viewer>

<button [disabled]="!canSave" (click)="save()">Save annotations</button>

Persisting Annotations​

Three methods cover the persistence story:

MethodReturnsPurpose
getAnnotations()Promise<any[]>Serialized state of every annotation created or modified in the editor
setAnnotations(annotations)Promise<{ restored: number; pending: number; rejected: number }>Restore previously exported annotations into the editor
getDocumentAsBlob()Promise<Blob>The current document bytes — including annotation edits — as a Blob

Exporting with getAnnotations()​

getAnnotations() returns a JSON-safe array (geometry arrays are plain arrays), so you can JSON.stringify it and send it to your server as-is.

Restoring with setAnnotations()​

setAnnotations() rebuilds each annotation on its own page. Important semantics:

  • Restore is additive. Calling it twice with the same payload creates duplicates — restore once per document load.
  • pending counts annotations targeting pages that haven't rendered yet; they apply automatically when those pages render.
  • rejected counts items with a pageIndex that is invalid for the current document — they are skipped.
  • Stamp images cannot round-trip. Their bitmaps are not serializable, so stamp annotations won't survive an export/restore cycle. If you need edits baked in permanently, save the whole document with getDocumentAsBlob() instead.

Complete Example: Save to Server, Restore on Reload​

import { Component, ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { PdfJsViewerComponent } from 'ng2-pdfjs-viewer';

@Component({
selector: 'app-document-review',
template: `
<ng2-pdfjs-viewer #pdfViewer
pdfSrc="assets/contract.pdf"
(onDocumentLoad)="restoreAnnotations()"
(onAnnotationEditorStateChange)="canSave = !$event.isEmpty || $event.hasSomethingToUndo">
</ng2-pdfjs-viewer>
<button [disabled]="!canSave" (click)="saveAnnotations()">Save</button>
`
})
export class DocumentReviewComponent {
@ViewChild('pdfViewer') pdfViewer!: PdfJsViewerComponent;
canSave = false;
documentId = 'contract-42';

constructor(private http: HttpClient) {}

async saveAnnotations() {
const annotations = await this.pdfViewer.getAnnotations();
await this.http
.put(`/api/documents/${this.documentId}/annotations`, annotations)
.toPromise();
}

async restoreAnnotations() {
const annotations = await this.http
.get<any[]>(`/api/documents/${this.documentId}/annotations`)
.toPromise();
if (annotations?.length) {
// Additive: only call once per document load
const result = await this.pdfViewer.setAnnotations(annotations);
console.log(
`restored ${result.restored}, pending ${result.pending}, rejected ${result.rejected}`
);
}
}
}

Uploading the Edited Document​

To persist the document itself — with annotations and form fills baked into the PDF bytes — use getDocumentAsBlob():

async uploadDocument() {
const blob = await this.pdfViewer.getDocumentAsBlob();
const formData = new FormData();
formData.append('file', blob, 'annotated.pdf');
await this.http.post('/api/upload', formData).toPromise();
}

Signatures (eSign)​

With enableSignatureEditor on, users can draw, type, or upload a signature and place it on the page. Placed signatures are saved as stamp-style annotations.

caution

This is an eSign convenience, not cryptographic signing. The signature is a visual annotation in the document — it does not produce a digital signature, certificate chain, or tamper-evident seal. If you need legally binding cryptographic signatures, pair the viewer with a server-side signing service.

Persisting Saved Signatures with signatureStorage​

By default, signatures the user saves for reuse live in the viewer iframe's localStorage. To store them server-side per user instead, provide a PdfSignatureStorage implementation via the [signatureStorage] input:

interface PdfSignatureStorage {
loadAll(): Promise<Record<string, unknown>>;
save(uuid: string, data: unknown): Promise<void>;
delete(uuid: string): Promise<void>;
}

data is PDF.js's serialized signature — treat it as an opaque JSON value.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { PdfSignatureStorage } from 'ng2-pdfjs-viewer';

@Injectable({ providedIn: 'root' })
export class ServerSignatureStorage implements PdfSignatureStorage {
constructor(private http: HttpClient) {}

loadAll(): Promise<Record<string, unknown>> {
return this.http
.get<Record<string, unknown>>('/api/me/signatures')
.toPromise() as Promise<Record<string, unknown>>;
}

async save(uuid: string, data: unknown): Promise<void> {
await this.http.put(`/api/me/signatures/${uuid}`, data).toPromise();
}

async delete(uuid: string): Promise<void> {
await this.http.delete(`/api/me/signatures/${uuid}`).toPromise();
}
}
export class MyComponent {
constructor(public signatureStorage: ServerSignatureStorage) {}
}
<ng2-pdfjs-viewer
pdfSrc="assets/contract.pdf"
[enableSignatureEditor]="true"
[signatureStorage]="signatureStorage">
</ng2-pdfjs-viewer>