---
title: "Render PDF annotations and forms with PDF.js AnnotationLayer"
canonical_url: "https://www.nutrient.io/blog/pdfjs-native-annotation-layer-forms/"
md_url: "https://www.nutrient.io/blog/pdfjs-native-annotation-layer-forms.md"
last_updated: "2026-07-22T10:51:42.259Z"
description: "Use the PDF.js AnnotationLayer to render native PDF annotations, interactive form fields, and XFA forms — with code for reading form data programmatically."
---

**TL;DR**

- The `AnnotationLayer` renders annotations that *already exist* in the PDF — links, form widgets, highlights, and sticky notes. It’s separate from the `AnnotationEditorLayer`, which creates new annotations.

- Set `annotationMode: AnnotationMode.ENABLE_FORMS` on `PDFViewer` to make form fields interactive; enable XFA with `enableXfa: true`.

- Read field values by iterating `page.getAnnotations()` and filtering on `subtype === "Widget"` — `fieldName`, `fieldValue`, and `fieldType` (`"Tx"`, `"Btn"`, `"Ch"`) are all there.

## Prerequisites

This guide assumes you have a `PDFViewer` from `pdfjs-dist/web/pdf_viewer.mjs` and a loaded `pdfDocument`. If you haven’t wired those up yet, start with our blog on [how to set up a custom PDF.js viewer in React](https://www.nutrient.io/blog/pdfjs-react-viewer-setup.md). For *creating* new annotations rather than rendering existing ones, see [the `AnnotationEditorLayer` guide](https://www.nutrient.io/blog/pdfjs-annotation-editor-layer/).

You’ll also need:

- `pdfjs-dist` 4.x or later

- A PDF with annotations or form fields to test against

## What the AnnotationLayer renders

| Annotation type               | Rendered as                         |
| ----------------------------- | ----------------------------------- |
| Link                          | Clickable `<a>` elements            |
| Text (popup note)             | Note icon with popup on click       |
| Highlight/underline/strikeout | Colored overlays                    |
| Free text                     | Positioned text block               |
| Widget (form fields)          | `<input>`, `<select>`, `<textarea>` |
| Stamp                         | Image                               |
| File attachment               | Download icon                       |

## Enabling the AnnotationLayer

The `PDFViewer` renders annotation layers by default. If building a custom page renderer, you enable it per page:

```tsx

import { AnnotationLayer } from "pdfjs-dist";

async function renderAnnotationLayer(page, viewport, container) {
  const annotations = await page.getAnnotations();

  const annotationLayerDiv = document.createElement("div");
  annotationLayerDiv.className = "annotationLayer";
  container.appendChild(annotationLayerDiv);

  const annotationLayer = new AnnotationLayer({
    div: annotationLayerDiv,
    accessibilityManager: null,
    annotationCanvasMap: null,
    page,
    viewport: viewport.clone({ dontFlip: true }),
  });

  await annotationLayer.render({
    annotations,
    div: annotationLayerDiv,
    page,
    viewport: viewport.clone({ dontFlip: true }),
    linkService,
  });
}

```

## Rendering interactive forms

PDF forms with text inputs, checkboxes, radio buttons, and dropdowns become fully interactive:

```tsx

const viewer = new pdfjs.PDFViewer({
  container,
  eventBus,
  linkService,
  findController,
  // Enable interactive form rendering
  annotationMode: pdfjs.AnnotationMode.ENABLE_FORMS,
});

```

### AnnotationMode options

`PDFViewer` accepts one of four annotation modes:

```tsx

const AnnotationMode = {
  DISABLE: 0,        // Don't render annotations at all.
  ENABLE: 1,         // Render annotations (read-only).
  ENABLE_FORMS: 2,   // Render annotations with interactive forms.
  ENABLE_STORAGE: 3, // Interactive forms with local storage persistence.
};

```

### Reading form values

After the user fills in a form, you can extract values:

```tsx

async function getFormData(pdfDocument) {
  const formData = {};

  for (let i = 1; i <= pdfDocument.numPages; i++) {
    const page = await pdfDocument.getPage(i);
    const annotations = await page.getAnnotations();

    for (const annot of annotations) {
      if (annot.subtype === "Widget") {
        formData[annot.fieldName] = {
          type: annot.fieldType, // "Tx" (text), "Btn" (button), "Ch" (choice).
          value: annot.fieldValue,
          options: annot.options, // For dropdowns/listboxes.
        };
      }
    }
  }

  return formData;
}

```

## XFA forms

PDF.js also supports XML Forms Architecture (XFA) — complex dynamic forms used in government and enterprise PDFs:

```tsx

const viewer = new pdfjs.PDFViewer({
  container,
  eventBus,
  linkService,
  enableXfa: true, // Enable XFA form rendering.
});

```

XFA forms are rendered as HTML rather than canvas, allowing full interactivity.

## Reading existing annotations

Access all annotations on a page for custom processing:

```tsx

async function listAnnotations(pdfDocument) {
  for (let i = 1; i <= pdfDocument.numPages; i++) {
    const page = await pdfDocument.getPage(i);
    const annotations = await page.getAnnotations();

    for (const annot of annotations) {
      console.log({
        type: annot.subtype,        // "Link", "Text", "Highlight", "Widget", etc.
        rect: annot.rect,           // [x1, y1, x2, y2] in PDF coordinates.
        contents: annot.contents,   // Popup text content.
        color: annot.color,         // `Uint8ClampedArray` of [r, g, b] (0–255).
        url: annot.url,             // For link annotations.
        fieldName: annot.fieldName, // For form widgets.
        fieldValue: annot.fieldValue,
      });
    }
  }
}

```

## Annotation appearance

Customize how annotations appear with CSS:

```css

/* Style popup annotations */.annotationLayer.popup {
  background-color: #FFFFCC;

  border: 1px solid #999;

  border-radius: 4px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
  max-width: 300px;
}

/* Style form fields */.annotationLayer.textWidgetAnnotation input {
  border: 1px solid #4A90D9;

  border-radius: 2px;
  padding: 2px 4px;
}.annotationLayer.buttonWidgetAnnotation.checkBox input {
  accent-color: #4A90D9;

}

```

## Key points

- The `AnnotationLayer` renders annotations that already exist in the PDF (not user-created ones).

- Use `AnnotationMode.ENABLE_FORMS` to make form fields interactive.

- `page.getAnnotations()` returns structured data for all annotations on a page.

- XFA forms need `enableXfa: true` on the viewer.

- The `AnnotationLayer` is separate from the `AnnotationEditorLayer` (editing) and any custom overlay layer.

- CSS classes on `.annotationLayer` let you customize the appearance of all native annotations.

## How Nutrient Web SDK handles annotations and forms

Nutrient Web SDK renders all annotation types and form widgets interactively without an `AnnotationMode` flag, and it exposes form values directly on the instance without requiring per-page iteration to collect them. Learn more about [forms](https://www.nutrient.io/sdk/solutions/forms/) for the full create-and-fill workflow.

```js

const formFields = await instance.getFormFields();

await instance.setFormFieldValues({
  "First Name": "Jane",
  "Last Name": "Doe",
  "Email": "jane@example.com",
});

```

Beyond rendering, the SDK supports [form validation](https://www.nutrient.io/guides/web/forms/javascript-validation.md), calculation fields, JavaScript actions, and [digital signatures](https://www.nutrient.io/guides/web/signatures.md) (PKCS#7) — features that PDF.js leaves to your application. For roundtripping form values back into the PDF binary, refer to our post on how to [export PDFs with embedded annotations](https://www.nutrient.io/blog/pdfjs-exporting-pdfs-embedded-annotations/).

[Learn more about Nutrient Web SDK](https://www.nutrient.io/sdk/web-overview/) | [Migration guide](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md) | [Contact Sales](https://www.nutrient.io/contact-sales/?=sdk)

## FAQ

#### What’s the difference between <code>AnnotationLayer</code> and <code>AnnotationEditorLayer</code>?

`AnnotationLayer` renders annotations that already exist in the PDF (read or fill, but not create). `AnnotationEditorLayer` is the editing UI for new annotations the user draws — free text, ink, stamp, highlight. They can coexist: Existing PDF annotations show through the `AnnotationLayer`, and new edits go through the `AnnotationEditorLayer`. See [the `AnnotationEditorLayer` guide](https://www.nutrient.io/blog/pdfjs-annotation-editor-layer/) for the creation side.

#### What does <code>annot.fieldType</code> actually contain?

The PDF specification defines these field type codes: `"Tx"` (text field), `"Btn"` (button — push button, checkbox, or radio button), `"Ch"` (choice — list box or dropdown), and `"Sig"` (signature). To distinguish push button from checkbox from radio, check the `annot.checkBox`, `annot.radioButton`, and `annot.pushButton` flags.

#### What’s <code>ENABLE_STORAGE</code> for?

`AnnotationMode.ENABLE_STORAGE` keeps form values in PDF.js’s internal `AnnotationStorage`, which survives across rerenders within the same session and feeds into `pdfDocument.saveDocument()`. Use it when you want users to fill a form. Then download the filled PDF without writing your own value-tracking layer.

#### Do XFA forms still matter?

They matter less than they used to. XFA was an Adobe-only format for dynamic forms, deprecated in PDF 2.0. You’ll still encounter XFA in legacy government and enterprise PDFs, so PDF.js’s `enableXfa: true` support is useful when migrating from older systems — but newer forms use AcroForm widgets (the `Widget` subtype) instead.

#### Why are my form values not persisting?

Either you haven’t set `annotationMode: ENABLE_STORAGE` (values stay only in the DOM input) or the form fields don’t have stable `fieldName` attributes. PDF.js keys storage by field name, so duplicate or empty names cause values to overwrite or vanish. Inspect `annot.fieldName` from `getAnnotations()` to confirm.

#### How do I export the filled form back to a PDF?

You have two options. First, `pdfDocument.saveDocument()` writes the current `AnnotationStorage` values into a new PDF byte stream. This works for forms PDF.js can fill in place. Second, for forms you can’t edit through PDF.js, or for richer roundtrips, read values, and then write a fresh PDF with [a library like annotpdf or pdf-lib](https://www.nutrient.io/blog/pdfjs-exporting-pdfs-embedded-annotations/).
---

## 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 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 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)
- [app.py](/blog/extract-text-from-pdf-using-python.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)
- [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 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)
- [Open an image.](/blog/how-to-use-tesseract-ocr-in-python.md)
- [From an HTML string.](/blog/html-in-pdf-format.md)
- [Javascript Pdf Libraries](/blog/javascript-pdf-libraries.md)
- [Linearized Pdf](/blog/linearized-pdf.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)
- [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 Annotation Editor Layer](/blog/pdfjs-annotation-editor-layer.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 Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.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)
- [or](/blog/sample-blog-updated.md)
- [Open an image file.](/blog/tesseract-python-guide.md)
- [Top 5 Javascript Pdf Viewers](/blog/top-5-javascript-pdf-viewers.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 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)

