---
title: "PDF.js EventBus guide: Events, hooks, and custom pub/sub patterns"
canonical_url: "https://www.nutrient.io/blog/pdfjs-eventbus-guide/"
md_url: "https://www.nutrient.io/blog/pdfjs-eventbus-guide.md"
last_updated: "2026-08-11T08:57:05.236Z"
description: "Learn how to use the PDF.js EventBus for page lifecycle events, scale changes, search results, and custom annotation state management in React applications."
---

**TL;DR**

- The `EventBus` is PDF.js’s internal pub/sub channel — every interaction (page change, zoom, render, search) fires through it.

- Subscribe with `eventBus.on(name, handler)` and clean up with `eventBus.off(name, handler)` in `useEffect` returns.

- Dispatch your own events (e.g. `annotationcolorchange`) to bridge React components and PDF.js’s vanilla JS class instances.

## Prerequisites

This guide assumes you have a `PDFViewer` composed from `pdfjs-dist/web/pdf_viewer.mjs` and an `EventBus` instance shared between the viewer, link service, and find controller. 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).

You’ll also need:

- `pdfjs-dist` 4.x or later

- A React app (the hook patterns below use React 18+ effect semantics)

## Creating an EventBus

Import the viewer layer and construct a single `EventBus` instance that every component will share:

```tsx

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

```

The `EventBus` is shared across all PDF.js components — `PDFViewer`, `PDFLinkService`, `PDFFindController` — and your own custom code.

## Subscribing and unsubscribing

Register a handler with `eventBus.on(name, handler)` and remove it with `eventBus.off(name, handler)`:

```tsx

// Subscribe.
eventBus.on("pagechanging", (evt) => {
  console.log("Now on page:", evt.pageNumber);
});

// Unsubscribe.
eventBus.off("pagechanging", handler);

```

## Built-in events reference

PDF.js fires a fixed set of built-in events. The tables below group the most useful ones — by page lifecycle, scale and rotation, and search — with their payloads and when each fires.

### Page lifecycle

These events fire as the document loads and as pages scroll into view and finish rendering:

| Event               | Payload                                   | When                                      |
| ------------------- | ----------------------------------------- | ----------------------------------------- |
| `pagesinit`         | `{}`                                      | All pages initialized after document load |
| `pagechanging`      | `{ pageNumber }`                          | User scrolls to a different page          |
| `pagerendered`      | `{ pageNumber, cssTransform, timestamp }` | A page canvas finishes rendering          |
| `textlayerrendered` | `{ pageNumber }`                          | Text layer overlay is ready on a page     |

### Scale and rotation

These events fire whenever the zoom level or page rotation changes:

| Event              | Payload                  | When                  |
| ------------------ | ------------------------ | --------------------- |
| `scalechanging`    | `{ scale, presetValue }` | Zoom level changes    |
| `rotationchanging` | `{ pagesRotation }`      | Page rotation changes |

### Search/find

These events drive find operations and report their results back to your UI:

| Event                    | Payload                                | When                                                            |
| ------------------------ | -------------------------------------- | --------------------------------------------------------------- |
| `find`                   | `{ type, query, caseSensitive,... }`  | You dispatch this to trigger search                             |
| `updatefindmatchescount` | `{ matchesCount: { current, total } }` | Match count updates                                             |
| `updatefindcontrolstate` | `{ state, matchesCount }`              | Find state changes (`FOUND`, `NOT_FOUND`, `WRAPPED`, `PENDING`) |

## Pattern: React hook for EventBus events

Wrap subscription and cleanup in a reusable hook so each listener is removed when the component unmounts:

```tsx

function useEventBus(eventBus, eventName, handler) {
  React.useEffect(() => {
    eventBus?.on(eventName, handler);
    return () => {
      eventBus?.off(eventName, handler);
    };
  }, [eventBus, eventName, handler]);
}

// Usage.
useEventBus(eventBus.current, "pagechanging", (evt) => {
  setCurrentPage(evt.pageNumber);
});

```

## Pattern: Custom events via EventBus

The `EventBus` isn’t limited to built-in events. You can dispatch your own custom events and use it as a cross-component communication channel:

```tsx

class PDFAnnotationState {
  #eventBus;

  #color = "red";

  #tool = null;

  #showAnnotations = true;

  constructor({ eventBus }) {
    this.#eventBus = eventBus;

  }

  get color() {
    return this.#color;

  }

  set color(newColor) {
    this.#color = newColor;

    this.#eventBus.dispatch("annotationcolorchange", {

      source: this,
      color: newColor,
    });
  }

  get tool() {
    return this.#tool;

  }

  set tool(newTool) {
    this.#tool = newTool;

    this.#eventBus.dispatch("annotationtoolchange", {

      source: this,
      tool: newTool,
    });
  }

  get showAnnotations() {
    return this.#showAnnotations;

  }

  set showAnnotations(show) {
    this.#showAnnotations = show;

    this.#eventBus.dispatch("annotationshowchange", {

      source: this,
      showAnnotations: show,
    });
  }
}

```

Then listen to these events from any component:

```tsx

function useAnnotationState(eventBus, annotationState) {
  const [color, setColor] = React.useState(annotationState?.color);
  const [tool, setTool] = React.useState(annotationState?.tool);
  const [show, setShow] = React.useState(annotationState?.showAnnotations);

  React.useEffect(() => {
    const onColor = (evt) => setColor(evt.color);
    const onTool = (evt) => setTool(evt.tool);
    const onShow = (evt) => setShow(evt.showAnnotations);

    eventBus?.on("annotationcolorchange", onColor);
    eventBus?.on("annotationtoolchange", onTool);
    eventBus?.on("annotationshowchange", onShow);

    return () => {
      eventBus?.off("annotationcolorchange", onColor);
      eventBus?.off("annotationtoolchange", onTool);
      eventBus?.off("annotationshowchange", onShow);
    };
  }, [eventBus, annotationState]);

  return { color, tool, show };
}

```

## Why EventBus instead of React state?

PDF.js components (`PDFViewer`, `PDFFindController`, etc.) are **not React components** — they’re vanilla JS class instances. They can’t subscribe to React state. The `EventBus` bridges this gap:

1. **Toolbar changes annotation color** → sets `annotationState.color = "blue"`

2. **Setter dispatches** `annotationcolorchange` via `EventBus`

3. **Annotation component** listens via `.on("annotationcolorchange")` and updates its React state

This pattern keeps your custom PDF features integrated with PDF.js’s own event system instead of creating a parallel state management layer.

## Key events for common features

This table maps common viewer features to the events you’ll typically subscribe to:

| Feature                 | Events to listen to                                |
| ----------------------- | -------------------------------------------------- |
| Page navigation toolbar | `pagechanging`                                     |
| Zoom controls           | `scalechanging`                                    |
| Custom overlay layers   | `textlayerrendered`, `pagerendered`                |
| Search results display  | `updatefindcontrolstate`, `updatefindmatchescount` |
| Annotation rendering    | `textlayerrendered` (to create overlay containers) |
| Highlight positioning   | `scalechanging`, `rotationchanging`                |

## Gotchas

- Always clean up listeners in `useEffect` return functions — PDF.js `EventBus` doesn’t clean up automatically

- The `dispatch` method requires a `source` property in the event object by convention

- `EventBus` events are synchronous — heavy handlers will block rendering

- `textlayerrendered` fires per page, not once for the whole document

## How Nutrient Web SDK handles this

Nutrient Web SDK exposes the same kinds of viewer events through standard DOM-style `addEventListener`/`removeEventListener` calls. Event names are TypeScript-typed, so your editor can complete them and flag typos.

```js

instance.addEventListener("viewState.currentPageIndex.change", (pageIndex) => {
  console.log("Now on page:", pageIndex + 1);
});

instance.addEventListener("viewState.zoom.change", (zoom) => {
  console.log("Zoom level:", zoom);
});

```

For broader viewer changes, listen to `viewState.change` and receive the full state diff. See the related guides on [search](https://www.nutrient.io/blog/pdfjs-text-search-pdffindcontroller.md) and [navigation, zoom, and rotation](https://www.nutrient.io/blog/pdfjs-navigation-zoom-rotation.md) for events that bridge into PDF.js `EventBus` events as well.

[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

#### Should I use <code>eventBus.on</code> or <code>eventBus._on</code>?

Use `eventBus.on`/`eventBus.off`. The `_on`/`_off` forms exist as legacy internal aliases, but PDF.js’s public documentation and viewer source use the non-underscored names. Sticking to the public form makes your code easier to read and less likely to break on a future PDF.js version.

#### Why does <code>dispatch</code> need a <code>source</code> property?

PDF.js’s own components inspect `evt.source` to avoid reacting to events they themselves dispatched (preventing feedback loops). When you call `eventBus.dispatch("myevent", { source: this,... })`, you’re following the same convention so downstream listeners can filter by origin.

#### Are <code>EventBus</code> events synchronous?

Yes. Handlers fire in the order they were subscribed, on the same microtask. Long-running work in a listener will block subsequent listeners and delay the rendering pipeline — wrap expensive operations in `queueMicrotask`, `setTimeout`, or `requestIdleCallback` when needed.

#### Does <code>textlayerrendered</code> fire once or per page?

Per page. Each `PageView` fires its own `textlayerrendered` event when its text layer overlay finishes rendering. That makes it a useful hook for inserting per-page overlays (highlights, annotations), but a poor signal for “the whole document is ready.”

#### Can I have multiple <code>EventBus</code> instances?

Technically yes, but you almost never want to. PDF.js’s viewer components are constructed with a shared `EventBus` so that `PDFViewer`, `PDFLinkService`, `PDFFindController`, and your code all see the same events. Separate buses defeat the purpose of having one.

#### What happens if I forget to call <code>eventBus.off</code>?

The handler keeps running for the lifetime of the `EventBus`, even after the React component that registered it has unmounted. That typically manifests as memory leaks and stale-state warnings (handler closes over old state). Always clean up in the `useEffect` return.
---

## 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 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 Document Outline Bookmarks Metadata](/blog/pdfjs-document-outline-bookmarks-metadata.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)

