---
title: "How to use the PDF.js AnnotationEditorLayer for native annotation editing"
canonical_url: "https://www.nutrient.io/blog/pdfjs-annotation-editor-layer/"
md_url: "https://www.nutrient.io/blog/pdfjs-annotation-editor-layer.md"
last_updated: "2026-08-11T08:57:05.236Z"
description: "Enable and customize the built-in PDF.js `AnnotationEditorLayer` for free text, ink, stamp, and highlight annotations, with a React toolbar and save-to-PDF."
---

**TL;DR**

- Enable PDF.js’s built-in `AnnotationEditorLayer` for free text, ink, stamp, and highlight annotations.

- Switch between editor tools via `switchannotationeditormode` and tune defaults with `switchannotationeditorparams`.

- Save the edited PDF (annotations included) with `pdfDocument.saveDocument()`.

## Prerequisites

This guide assumes you’ve already wired up a PDF.js viewer — `PDFViewer`, `EventBus`, `PDFLinkService`, and a loaded `pdfDocument`. If you haven’t, start with our guide on [how to set up a custom PDF.js viewer in React](https://www.nutrient.io/blog/pdfjs-react-viewer-setup.md).

You’ll also need:

- `pdfjs-dist` 4.x or later (`HIGHLIGHT` editor landed late in the 3.x line; 4.x is the safe baseline)

- `pdfjs-dist/web/pdf_viewer.css` imported so the editor layer renders correctly

## What it provides

| Annotation type | Description                                     |
| --------------- | ----------------------------------------------- |
| **Free text**   | Editable text boxes placed anywhere on the page |
| **Ink**         | Freehand drawing (pen tool)                     |
| **Stamp**       | Image annotations dropped onto pages            |
| **Highlight**   | Native text highlight annotations               |

## Enabling the AnnotationEditor

When creating your `PDFViewer`, pass the `AnnotationEditorMode`:

```tsx

const pdfjs = await import("pdfjs-dist/web/pdf_viewer.mjs");

const viewer = new pdfjs.PDFViewer({
  container: document.getElementById("pdf-container"),
  eventBus,
  linkService,
  findController,
  annotationEditorMode: pdfjs.AnnotationEditorType.NONE, // Start with editing disabled.
});

```

## Switching editor modes

Toggle between annotation types by dispatching `switchannotationeditormode` on the `EventBus`:

```tsx

// Enable free text editor.
eventBus.dispatch("switchannotationeditormode", {
  source: this,
  mode: pdfjs.AnnotationEditorType.FREETEXT,
});

// Enable ink (drawing) editor.
eventBus.dispatch("switchannotationeditormode", {
  source: this,
  mode: pdfjs.AnnotationEditorType.INK,
});

// Enable stamp (image) editor.
eventBus.dispatch("switchannotationeditormode", {
  source: this,
  mode: pdfjs.AnnotationEditorType.STAMP,
});

// Enable highlight editor.
eventBus.dispatch("switchannotationeditormode", {
  source: this,
  mode: pdfjs.AnnotationEditorType.HIGHLIGHT,
});

// Disable all editors (return to view mode).
eventBus.dispatch("switchannotationeditormode", {
  source: this,
  mode: pdfjs.AnnotationEditorType.NONE,
});

```

## AnnotationEditorType enum

The numeric values used by `switchannotationeditormode` are non-sequential because the enum grew across PDF.js releases:

```tsx

const AnnotationEditorType = {
  DISABLE: -1,
  NONE: 0,
  FREETEXT: 3,
  STAMP: 13,
  INK: 15,
  HIGHLIGHT: 9,
};

```

## Customizing free text annotations

You can set default properties for new free text annotations via the `EventBus`:

```tsx

// Set default font size.
eventBus.dispatch("switchannotationeditorparams", {
  source: this,
  type: pdfjs.AnnotationEditorParamsType.FREETEXT_SIZE,
  value: 14,
});

// Set default font color.
eventBus.dispatch("switchannotationeditorparams", {
  source: this,
  type: pdfjs.AnnotationEditorParamsType.FREETEXT_COLOR,
  value: "#FF0000",

});

```

## Customizing ink annotations

Use `switchannotationeditorparams` to set the color, thickness, and opacity for new ink strokes:

```tsx

// Set ink color.
eventBus.dispatch("switchannotationeditorparams", {
  source: this,
  type: pdfjs.AnnotationEditorParamsType.INK_COLOR,
  value: "#0000FF",

});

// Set ink thickness.
eventBus.dispatch("switchannotationeditorparams", {
  source: this,
  type: pdfjs.AnnotationEditorParamsType.INK_THICKNESS,
  value: 3,
});

// Set ink opacity.
eventBus.dispatch("switchannotationeditorparams", {
  source: this,
  type: pdfjs.AnnotationEditorParamsType.INK_OPACITY,
  value: 80, // 0-100
});

```

## Customizing highlight annotations

Highlight annotations support one parameter — color:

```tsx

// Set highlight color.
eventBus.dispatch("switchannotationeditorparams", {
  source: this,
  type: pdfjs.AnnotationEditorParamsType.HIGHLIGHT_COLOR,
  value: "#FFFF00",

});

```

## Saving annotations

The built-in editor layer can serialize all annotations directly into the PDF. Use `PDFDocumentProxy.saveDocument()`:

```tsx

// Save the PDF with all editor annotations baked in.
const data = await pdfDocument.saveDocument();
// data is a Uint8Array of the modified PDF.

// Download it.
const blob = new Blob([data], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "annotated.pdf";
a.click();
URL.revokeObjectURL(url);

```

Because the editor layer ships with PDF.js, you no longer need a separate library to embed annotations into the saved file.

## React toolbar example

A minimal React toolbar that wires buttons to `switchannotationeditormode` and tracks the active tool in state:

```tsx

function AnnotationEditorToolbar() {
  const { eventBus } = useContext(PDFContext);
  const [activeMode, setActiveMode] = useState("NONE");

  const setMode = (mode) => {
    const modeValue = pdfjs.AnnotationEditorType[mode];
    eventBus.dispatch("switchannotationeditormode", {
      source: null,
      mode: modeValue,
    });
    setActiveMode(mode);
  };

  return (
    <div>
      <button
        className={activeMode === "FREETEXT"? "active" : ""}
        onClick={() => setMode("FREETEXT")}
      >
        Text
      </button>
      <button
        className={activeMode === "INK"? "active" : ""}
        onClick={() => setMode("INK")}
      >
        Draw
      </button>
      <button
        className={activeMode === "HIGHLIGHT"? "active" : ""}
        onClick={() => setMode("HIGHLIGHT")}
      >
        Highlight
      </button>
      <button
        className={activeMode === "STAMP"? "active" : ""}
        onClick={() => setMode("STAMP")}
      >
        Image
      </button>
      <button onClick={() => setMode("NONE")}>
        Done
      </button>
    </div>
  );
}

```

## When to use built-in vs. custom annotations

| Scenario                                | Recommendation                                                           |
| --------------------------------------- | ------------------------------------------------------------------------ |
| Simple highlighting + free text         | Built-in `AnnotationEditorLayer`                                         |
| Collaborative annotations with comments | Custom layer (need per-annotation metadata, authors, threads)            |
| Annotations synchronized to a database  | Custom layer (built-in doesn’t expose annotation events for persistence) |
| PDF export with annotations             | Built-in `saveDocument()`                                                |
| Quick markup for personal use           | Built-in `AnnotationEditorLayer`                                         |

## CSS

The annotation editor layer requires its own CSS:

```tsx

import "pdfjs-dist/web/pdf_viewer.css";

```

The default CSS includes styles for `.annotationEditorLayer`, editor toolbars, and annotation handles.

## Key points

- `AnnotationEditorLayer` has been built into PDF.js since version 3.x — no extra dependencies.

- Switch modes by dispatching `switchannotationeditormode` on the `EventBus`.

- Customize colors, sizes, and opacity via `switchannotationeditorparams`.

- `pdfDocument.saveDocument()` serializes all annotations into the PDF — no separate library required.

- The built-in layer handles all coordinate conversion, scaling, and rotation automatically.

- For complex workflows (comments, collaboration, and database synchronization), a custom layer still makes sense.

- For simple markup and export, the built-in layer is significantly less code.

## How Nutrient Web SDK handles this

Nutrient Web SDK ships its annotation tools as part of the viewer rather than as a separate editor layer you opt into. Switching tools is a single `setViewState` call, and annotation events (`create`, `update`, `delete`) are exposed on the instance for autosave and collaboration.

```js

const instance = await NutrientViewer.load({
  container: "#pdf-container",

  document: "document.pdf",
});

instance.setViewState((v) =>
  v.set("interactionMode", NutrientViewer.InteractionMode.INK),
);

instance.addEventListener("annotations.create", (annotations) => {
  // Persist to your backend or synchronize via Instant.
});

```

Compared to the built-in editor layer’s four types (free text, ink, stamp, highlight), Nutrient provides 17+ annotation types with comment threads, @mentions, and real-time synchronization via Instant. Annotation export and embedding are handled by the SDK.

## FAQ

#### Which PDF.js version do I need for the <code>AnnotationEditorLayer</code>?

The editor layer landed in PDF.js 3.x, but `HIGHLIGHT` arrived later in that line. Use `pdfjs-dist` 4.x or later for the full set of free text, ink, stamp, and highlight annotation types.

#### How do I detect when annotations change so I can autosave?

The editor layer doesn’t expose per-annotation events directly. The two practical hooks are (1) the `annotationeditorstateschanged` `EventBus` event for tool-state transitions, and (2) calling `pdfDocument.saveDocument()` on a debounced timer or on `beforeunload`. For real per-annotation `create`/`update`/`delete` events, you need a custom annotation layer.

#### Can I preload existing annotations into the editor?

Annotations already embedded in the PDF are read and shown by the editor layer when the document loads. There’s no public API to inject annotations programmatically — you’d have to write them into the PDF first (using `pdf-lib` or similar) and then open the modified file in PDF.js.

#### What’s the difference between <code>AnnotationEditorType.NONE</code> and <code>DISABLE</code>?

`NONE` (0) keeps the editor layer mounted but with no tool active — users can still click existing annotations to edit them. `DISABLE` (-1) removes the editor layer entirely, putting the viewer in read-only mode.

#### Does <code>saveDocument()</code> produce a flat PDF or one with editable annotations?

It produces a PDF with the new annotations encoded as standard PDF annotation objects (`FreeText`, `Ink`, `Stamp`, `Highlight`). Other PDF viewers can read and continue editing them — they’re not rasterized.

#### How do I customize the toolbar UI?

PDF.js’s default viewer UI exposes a toolbar; if you’re using your own (the React example above), call `eventBus.dispatch("switchannotationeditormode",...)` and `"switchannotationeditorparams"` from your own buttons. There’s no built-in component to drop into a custom layout.

---

_See [Nutrient Web SDK](https://www.nutrient.io/sdk/web-overview/) for built-in annotations with comment threads and real-time synchronization, follow the [migration guide](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md) to switch from PDF.js, or [talk to Sales](https://www.nutrient.io/contact-sales/?=sdk) about your requirements._
---

## Related pages

- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Accessibility Untangled Why It Matters Guide](/blog/accessibility-untangled-why-it-matters-guide.md)
- [Advanced Techniques For React Native Ui Components](/blog/advanced-techniques-for-react-native-ui-components.md)
- [`vector_store` holds your indexed documents (see the multimodal RAG post](/blog/agentic-rag.md)
- [Ai Document Automation Extraction To Action](/blog/ai-document-automation-extraction-to-action.md)
- [Ai Legal Assistant Document Authoring](/blog/ai-legal-assistant-document-authoring.md)
- [Angular File Viewer Pdf Image Office Files](/blog/angular-file-viewer-pdf-image-office-files.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.md)
- [Best Document Ai Platforms](/blog/best-document-ai-platforms.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [1. Extract and chunk the PDF.](/blog/chat-with-pdf.md)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Construction Document Data Extraction](/blog/construction-document-data-extraction.md)
- [Convert One Drive Files To Pdf In Sharepoint](/blog/convert-one-drive-files-to-pdf-in-sharepoint.md)
- [Create And Edit Pdfs In Flutter](/blog/create-and-edit-pdfs-in-flutter.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [Creating A Document Scanner With Ocr In Python](/blog/creating-a-document-scanner-with-ocr-in-python.md)
- [Creating And Filling Pdf Forms Programmatically In Javascript](/blog/creating-and-filling-pdf-forms-programmatically-in-javascript.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Digital Workflow Automation](/blog/digital-workflow-automation.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Document Extraction Confidence Scores](/blog/document-extraction-confidence-scores.md)
- [Document Viewer](/blog/document-viewer.md)
- [Document Watermarking](/blog/document-watermarking.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [Extract Patient Data On Premises](/blog/extract-patient-data-on-premises.md)
- [app.py](/blog/extract-text-from-pdf-using-python.md)
- [Fillable Pdf](/blog/fillable-pdf.md)
- [How To Add Digital Signature To Pdf Using React](/blog/how-to-add-digital-signature-to-pdf-using-react.md)
- [How To Build A Dotnet Maui Pdf Viewer](/blog/how-to-build-a-dotnet-maui-pdf-viewer.md)
- [How To Build A Flutter Pdf Viewer](/blog/how-to-build-a-flutter-pdf-viewer.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [How To Build A Javascript Pdf Viewer](/blog/how-to-build-a-javascript-pdf-viewer.md)
- [or](/blog/how-to-build-a-nextjs-pdf-viewer.md)
- [How To Build A Powerpoint Viewer Using Javascript](/blog/how-to-build-a-powerpoint-viewer-using-javascript.md)
- [Using Yarn](/blog/how-to-build-a-react-excel-viewer.md)
- [How To Build A React Native Pdf Viewer](/blog/how-to-build-a-react-native-pdf-viewer.md)
- [How To Build A React Powerpoint Viewer](/blog/how-to-build-a-react-powerpoint-viewer.md)
- [How To Build A Reactjs File Viewer](/blog/how-to-build-a-reactjs-file-viewer.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer With Pdfjs](/blog/how-to-build-a-vuejs-pdf-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer](/blog/how-to-build-a-vuejs-pdf-viewer.md)
- [How To Build An Android Pdf Viewer](/blog/how-to-build-an-android-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Ng2 Pdf Viewer](/blog/how-to-build-an-angular-pdf-viewer-with-ng2-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Pdfjs](/blog/how-to-build-an-angular-pdf-viewer-with-pdfjs.md)
- [How To Convert Docx To Pdf Using Javascript](/blog/how-to-convert-docx-to-pdf-using-javascript.md)
- [How To Convert Docx To Pdf Using Python](/blog/how-to-convert-docx-to-pdf-using-python.md)
- [How To Convert Html To Pdf Using Html2pdf](/blog/how-to-convert-html-to-pdf-using-html2pdf.md)
- [or](/blog/how-to-convert-html-to-pdf-using-react.md)
- [How To Convert Html To Pdf Using Wkhtmltopdf And Csharp](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-csharp.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Convert Word To Pdf In Nodejs](/blog/how-to-convert-word-to-pdf-in-nodejs.md)
- [or](/blog/how-to-create-a-react-js-signature-pad.md)
- [How To Create Pdfs With React To Pdf](/blog/how-to-create-pdfs-with-react-to-pdf.md)
- [How To Edit Pdfs Using Ios Pdf Library](/blog/how-to-edit-pdfs-using-ios-pdf-library.md)
- [How To Embed A Pdf Viewer In Your Website](/blog/how-to-embed-a-pdf-viewer-in-your-website.md)
- [How To Extract Tables From Pdf And Images](/blog/how-to-extract-tables-from-pdf-and-images.md)
- [How To Generate Pdf From Html With Nodejs](/blog/how-to-generate-pdf-from-html-with-nodejs.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [How To Merge Pdfs Using Javascript](/blog/how-to-merge-pdfs-using-javascript.md)
- [How To Ocr Pdfs In Linux](/blog/how-to-ocr-pdfs-in-linux.md)
- [How To Print Pdf In Csharp](/blog/how-to-print-pdf-in-csharp.md)
- [Open an image.](/blog/how-to-use-tesseract-ocr-in-python.md)
- [From an HTML string.](/blog/html-in-pdf-format.md)
- [Javascript Pdf Editors](/blog/javascript-pdf-editors.md)
- [Javascript Pdf Libraries](/blog/javascript-pdf-libraries.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [or](/blog/merge-pdfs.md)
- [Swift Package Manager](/blog/mobile-pdf-sdk.md)
- [`elements` come from your document parser — each has a type and content.](/blog/multimodal-rag.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [Building WCAG 2.2, Section 508, and PDF/UA-compliant PDFs with an SDK](/blog/pdf-accessibility.md)
- [Pdf Data Extraction Developer Guide](/blog/pdf-data-extraction-developer-guide.md)
- [Pdf Extraction Benchmark Opendataloader Bench](/blog/pdf-extraction-benchmark-opendataloader-bench.md)
- [Pdf Extraction Document Case Studies](/blog/pdf-extraction-document-case-studies.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdfjs Accessibility Structtree Printing](/blog/pdfjs-accessibility-structtree-printing.md)
- [Pdfjs Advanced Loading Streaming Workers](/blog/pdfjs-advanced-loading-streaming-workers.md)
- [Pdfjs Area Annotations Canvas Capture](/blog/pdfjs-area-annotations-canvas-capture.md)
- [Pdfjs Coordinate Systems Pdf To Screen](/blog/pdfjs-coordinate-systems-pdf-to-screen.md)
- [Pdfjs Document Outline Bookmarks Metadata](/blog/pdfjs-document-outline-bookmarks-metadata.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [macOS](/blog/pdfjs-file-format-conversion-to-pdf.md)
- [macOS](/blog/pdfjs-generating-pdf-thumbnails-pdf2pic.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs Native Annotation Layer Forms](/blog/pdfjs-native-annotation-layer-forms.md)
- [Pdfjs Navigation Zoom Rotation](/blog/pdfjs-navigation-zoom-rotation.md)
- [Pdfjs Pdf Page Manipulation Pdf Lib](/blog/pdfjs-pdf-page-manipulation-pdf-lib.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdfjs Rendering Overlays React Portals](/blog/pdfjs-rendering-overlays-react-portals.md)
- [Pdfjs Server Side Text Extraction](/blog/pdfjs-server-side-text-extraction.md)
- [Pdfjs Sticky Note Annotations](/blog/pdfjs-sticky-note-annotations.md)
- [Pdfjs Text Highlight Annotations](/blog/pdfjs-text-highlight-annotations.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Pdfjs Thumbnail Sidebar](/blog/pdfjs-thumbnail-sidebar.md)
- [Process Flows](/blog/process-flows.md)
- [React Native Pdf Annotation](/blog/react-native-pdf-annotation.md)
- [Using Yarn](/blog/react-pdf-editor.md)
- [or](/blog/sample-blog-updated.md)
- [Sdk Product Updates Q2 2026](/blog/sdk-product-updates-q2-2026.md)
- [Add DWS MCP Server to your Claude Code project.](/blog/teaching-llms-to-read-pdfs.md)
- [Open an image file.](/blog/tesseract-python-guide.md)
- [Define the HTML part of the document.](/blog/top-10-ways-to-generate-pdfs-in-python.md)
- [Top 5 Javascript Pdf Viewers](/blog/top-5-javascript-pdf-viewers.md)
- [or](/blog/top-js-pdf-libraries.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)
- [What Is Document Processing](/blog/what-is-document-processing.md)
- [What Is Intelligent Document Processing](/blog/what-is-intelligent-document-processing.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)
- [Why Pdfium Is A Trusted Platform For Pdf Rendering](/blog/why-pdfium-is-a-trusted-platform-for-pdf-rendering.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)

