Jesse A. Green / notebook

PDF.js User Guide

Generated June 2026 from mozilla/pdf.js at 9071f45

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

PDF.js is a PDF rendering engine written entirely in JavaScript. It parses and renders Portable Document Format (PDF) files in any modern web browser — and in Node.js — using only standard web technologies (HTML5 Canvas, Web Workers, and WebAssembly), with no native plugins and no server-side processing. It is the same engine that powers the built-in PDF viewer in Mozilla Firefox.

This guide shows you how to use PDF.js in two ways:

  • As a library (pdfjs-dist) that you embed in your own web app or Node.js script to load, render, and extract data from PDFs.
  • As a ready-made web viewer — a complete, drop-in PDF reader with a toolbar, search, thumbnails, printing, and annotation editing.

Note

PDF.js is a community-driven project maintained by Mozilla. It is distributed under the Apache License 2.0, which permits commercial and private use, modification, and redistribution.


Table of Contents

  1. Quick Start
  2. Installation
  3. Core Concepts (read this first)
  4. Loading a PDF Document
  5. Rendering Pages to a Canvas
  6. Extracting Text
  7. Reading Document Information
  8. Working with Annotations, Links, and Forms
  9. Password-Protected PDFs
  10. Tracking Load Progress
  11. Using PDF.js in Node.js
  12. The Prebuilt Web Viewer
  13. Common Tasks (Recipes)
  14. Advanced Usage
  15. Troubleshooting
  16. Frequently Asked Questions
  17. API Reference

1. Quick Start

There are two distinct starting points depending on what you want.

1.1 "I just want to render a PDF onto my own page"

Use the library. The minimal flow is always the same: get a document, get a page, render the page to a <canvas>.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>PDF.js minimal example</title>
</head>
<body>
  <canvas id="the-canvas"></canvas>

  <script type="module">
    import * as pdfjsLib from "https://cdn.jsdelivr.net/npm/pdfjs-dist/build/pdf.min.mjs";

    // Tell the library where the worker script lives (required).
    pdfjsLib.GlobalWorkerOptions.workerSrc =
      "https://cdn.jsdelivr.net/npm/pdfjs-dist/build/pdf.worker.min.mjs";

    const loadingTask = pdfjsLib.getDocument("helloworld.pdf");
    const pdf = await loadingTask.promise;

    // Pages are 1-based: the first page is page 1.
    const page = await pdf.getPage(1);

    const scale = 1.5;
    const viewport = page.getViewport({ scale });

    const canvas = document.getElementById("the-canvas");
    const context = canvas.getContext("2d");
    canvas.width = Math.floor(viewport.width);
    canvas.height = Math.floor(viewport.height);

    await page.render({ canvas, canvasContext: context, viewport }).promise;
    console.log("Page rendered");
  </script>
</body>
</html>
Page rendered

Important

You must set GlobalWorkerOptions.workerSrc before calling getDocument. PDF.js does all parsing and rendering preparation on a separate Web Worker thread, and it needs to know where to find the worker file. If you skip this, PDF.js falls back to a slower "fake worker" that runs on the main thread and logs Warning: Setting up fake worker.

1.2 "I want a complete PDF reader UI"

Use the prebuilt viewer. Download a prebuilt package (see Installation), then open web/viewer.html in a browser. To display a specific file, pass it in the URL hash:

web/viewer.html?file=path/to/document.pdf

The viewer gives you page navigation, zoom, search, a thumbnail/outline sidebar, printing, downloading, rotation, and an annotation editor — with no code required. See The Prebuilt Web Viewer.


2. Installation

PDF.js is published on npm as pdfjs-dist (the prebuilt distribution). There are several ways to obtain it.

npm / yarn

npm install pdfjs-dist
yarn add pdfjs-dist

The package contains:

  • build/pdf.mjs — the main library (the API you import).
  • build/pdf.worker.mjs — the worker engine (must be reachable at runtime).
  • build/pdf.sandbox.mjs — the scripting sandbox (only needed for PDFs with embedded JavaScript).
  • web/ — the complete prebuilt viewer application.
  • cmaps/, standard_fonts/, iccs/, wasm/ — supporting data assets (see below).

CDN (no build step)

<script type="module">
  import * as pdfjsLib from "https://cdn.jsdelivr.net/npm/pdfjs-dist/build/pdf.min.mjs";
  pdfjsLib.GlobalWorkerOptions.workerSrc =
    "https://cdn.jsdelivr.net/npm/pdfjs-dist/build/pdf.worker.min.mjs";
</script>

Using a CDN is the fastest way to experiment. For production, prefer bundling a pinned version so behavior is reproducible.

Prebuilt viewer ZIP

Download a prebuilt package from the project's releases, unzip it, and serve the folder over HTTP. Open web/viewer.html to use the full viewer.

Tip

The files ending in .min.mjs are minified for production. During development, use the non-minified pdf.mjs / pdf.worker.mjs so error messages and stack traces are readable.

2.1 Requirements

Requirement Detail
Browser Any modern, evergreen browser (current Firefox, Chrome, Edge, Safari). PDF.js targets standard ES modules, Web Workers, and Canvas.
Module type The distribution ships as ES modules (.mjs). Load with <script type="module"> or import.
Node.js (optional) Version 22.13.0 or newer, or any 24+ release, for server-side use.
HTTP server PDFs must usually be served over http:///https://. Opening pages from the file:// protocol breaks Web Workers and fetch.

Caution

Browsers enforce the same-origin policy when PDF.js fetches a PDF by URL. To load a PDF from another domain, that server must send the appropriate CORS headers (Access-Control-Allow-Origin). If you can't change the remote server, proxy the file through your own origin.

2.2 Supporting asset folders

For full fidelity on every PDF in the wild, the library fetches a few data folders on demand. Point the API at them with the matching option (covered in API Reference):

Folder Option Needed for
cmaps/ cMapUrl PDFs using predefined Adobe CMaps (often CJK — Chinese/Japanese/Korean — text).
standard_fonts/ standardFontDataUrl PDFs that reference one of the 14 standard PDF fonts without embedding them.
iccs/ iccUrl Documents with ICC color profiles.
wasm/ wasmUrl WebAssembly-accelerated image decoding (JPEG 2000, JBIG2, OpenJPEG).

Note

If you don't load these folders, most PDFs still render fine. They matter for CJK text, non-embedded standard fonts, color-managed documents, and certain compressed images. When bundling with a tool like webpack, copy these folders into your output and set the four URLs to point at them.


3. Core Concepts (read this first)

A few ideas recur throughout the API. Understanding them up front makes everything else obvious.

Everything is asynchronous and promise-based. : Loading, page access, rendering, and text extraction all return promises (or task objects that expose a .promise). Use async/await.

Two threads: main and worker. : The code you call lives on the main thread. The heavy lifting — parsing the PDF, decoding fonts and images, building the list of drawing operations — happens on a Web Worker. The objects you hold (PDFDocumentProxy, PDFPageProxy) are proxies: thin handles that message the worker on your behalf. This is why workerSrc must be set.

Pages are 1-based. : pdf.getPage(1) returns the first page. pdf.numPages is the total count.

A viewport defines scale and rotation. : Before you render or measure a page, you ask it for a viewport at a given scale (and optional rotation). The viewport carries the pixel width/height and the transform PDF.js uses to draw. PDF "user space" units are 1/72 inch, so scale: 1 means 72 DPI; scale: 1.5 ≈ 108 DPI.

Rendering returns a cancellable task. : page.render(...) returns a RenderTask. Await renderTask.promise for completion, or call renderTask.cancel() to abort (useful when the user scrolls away or zooms before a render finishes).

Clean up when you're done. : Call page.cleanup() to release a page's resources and pdf.destroy() (via the loading task) or loadingTask.destroy() to tear down the worker. This matters most in long-lived single-page apps.

getDocument(src)  ──►  PDFDocumentLoadingTask
                            │  .promise
                            ▼
                       PDFDocumentProxy   (numPages, metadata, outline, …)
                            │  .getPage(n)
                            ▼
                        PDFPageProxy      (getViewport, render, getTextContent, …)
                            │  .render({ canvas, viewport })
                            ▼
                         RenderTask        (.promise, .cancel())

4. Loading a PDF Document

The single entry point is getDocument(). It accepts either a URL/path string or a parameter object, and returns a PDFDocumentLoadingTask. Await its .promise to get a PDFDocumentProxy.

4.1 The four ways to supply PDF data

From a URL

const loadingTask = pdfjsLib.getDocument("https://example.com/doc.pdf");
const pdf = await loadingTask.promise;

Equivalent explicit form, which you need when you also pass other options:

const loadingTask = pdfjsLib.getDocument({ url: "https://example.com/doc.pdf" });

From binary data (TypedArray)

const bytes = new Uint8Array(await file.arrayBuffer());
const pdf = await pdfjsLib.getDocument({ data: bytes }).promise;

Use this for files chosen via an <input type="file">, fetched yourself, or generated in memory.

From a Base64 string

const raw = atob(base64String);           // decode to a binary string
const bytes = Uint8Array.from(raw, c => c.charCodeAt(0));
const pdf = await pdfjsLib.getDocument({ data: bytes }).promise;

From a custom range transport

// Advanced: feed bytes on demand (e.g. your own chunked protocol).
const pdf = await pdfjsLib.getDocument({ range: myRangeTransport }).promise;

See PDFDataRangeTransport for streaming very large files.

Warning

When you pass a TypedArray as data, PDF.js transfers ownership of that buffer to the worker thread. After the call, your original array is detached and unusable. If you still need the bytes, pass a copy: new Uint8Array(bytes).

4.2 Loading from a file input

<input type="file" id="picker" accept="application/pdf" />
<canvas id="view"></canvas>
document.getElementById("picker").addEventListener("change", async (e) => {
  const file = e.target.files[0];
  if (!file) return;

  const data = new Uint8Array(await file.arrayBuffer());
  const pdf = await pdfjsLib.getDocument({ data }).promise;

  const page = await pdf.getPage(1);
  const viewport = page.getViewport({ scale: 1.25 });
  const canvas = document.getElementById("view");
  const context = canvas.getContext("2d");
  canvas.width = viewport.width;
  canvas.height = viewport.height;
  await page.render({ canvas, canvasContext: context, viewport }).promise;
});

4.3 Common load-time options

You can pass any of these alongside url or data. The full list is in the API Reference.

const pdf = await pdfjsLib.getDocument({
  url: "report.pdf",
  password: "secret",          // for encrypted PDFs (see §9)
  withCredentials: true,       // send cookies/auth headers with the request
  httpHeaders: { Authorization: "Bearer …" },
  cMapUrl: "/pdfjs/cmaps/",    // CJK support
  cMapPacked: true,
  standardFontDataUrl: "/pdfjs/standard_fonts/",
  verbosity: pdfjsLib.VerbosityLevel.ERRORS, // quiet the console
}).promise;

5. Rendering Pages to a Canvas

Rendering always follows the same three steps: get the page → build a viewport → render.

5.1 The standard render

async function renderPage(pdf, pageNumber, canvas, scale = 1.5) {
  const page = await pdf.getPage(pageNumber);
  const viewport = page.getViewport({ scale });

  const context = canvas.getContext("2d");
  canvas.width = Math.floor(viewport.width);
  canvas.height = Math.floor(viewport.height);

  const renderTask = page.render({ canvas, canvasContext: context, viewport });
  await renderTask.promise;
  return page;
}

5.2 Rendering crisply on high-DPI (Retina) screens

A canvas looks blurry on high-density displays unless you scale by the device pixel ratio.

const page = await pdf.getPage(1);
const scale = 1.5;
const outputScale = window.devicePixelRatio || 1;
const viewport = page.getViewport({ scale });

canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = Math.floor(viewport.width) + "px";
canvas.style.height = Math.floor(viewport.height) + "px";

const transform = outputScale !== 1
  ? [outputScale, 0, 0, outputScale, 0, 0]
  : null;

await page.render({ canvas, canvasContext: canvas.getContext("2d"), viewport, transform }).promise;

Tip

PDF.js exports an OutputScale helper (pdfjsLib.OutputScale) and a PixelsPerInch constant that encapsulate this device-pixel math, used by the bundled viewer.

5.3 Rendering every page

const pdf = await pdfjsLib.getDocument("multi.pdf").promise;
const container = document.getElementById("pages");

for (let n = 1; n <= pdf.numPages; n++) {
  const page = await pdf.getPage(n);
  const viewport = page.getViewport({ scale: 1.2 });

  const canvas = document.createElement("canvas");
  canvas.width = viewport.width;
  canvas.height = viewport.height;
  container.append(canvas);

  await page.render({ canvas, canvasContext: canvas.getContext("2d"), viewport }).promise;
  page.cleanup(); // release worker-side resources for this page
}

5.4 Rotating a page

Pass a rotation (degrees, multiples of 90) to getViewport. It is added to the page's intrinsic rotation.

const viewport = page.getViewport({ scale: 1, rotation: 90 });

5.5 Cancelling and re-rendering

A canvas can only host one render at a time. When the user zooms or scrolls quickly, cancel the in-flight render before starting a new one.

let currentTask = null;

async function show(page, scale) {
  currentTask?.cancel();              // abort any render in progress
  const viewport = page.getViewport({ scale });
  canvas.width = viewport.width;
  canvas.height = viewport.height;

  currentTask = page.render({ canvas, canvasContext: canvas.getContext("2d"), viewport });
  try {
    await currentTask.promise;
  } catch (err) {
    if (err?.name === "RenderingCancelledException") {
      return; // expected — a newer render superseded this one
    }
    throw err;
  }
}

Note

A cancelled render rejects its promise with a RenderingCancelledException. This is normal control flow, not an error — check for it and ignore it.

5.6 Controlling which annotations are drawn

The annotationMode option decides whether form fields and annotations are painted onto the canvas. Use the AnnotationMode enum:

await page.render({
  canvas, canvasContext: context, viewport,
  annotationMode: pdfjsLib.AnnotationMode.ENABLE_FORMS,
}).promise;
Mode Effect
AnnotationMode.DISABLE Draw no annotations at all.
AnnotationMode.ENABLE Draw all annotations (default).
AnnotationMode.ENABLE_FORMS Draw annotations except interactive form widgets (you render those yourself in an HTML layer).
AnnotationMode.ENABLE_STORAGE Like ENABLE, but form fields reflect user-entered values held in the document's annotation storage — used for printing and saving.

6. Extracting Text

PDF.js can pull the text out of a page, with positioning information. This powers search, copy/paste, accessibility layers, and text-mining.

6.1 Get all text on a page

const page = await pdf.getPage(1);
const textContent = await page.getTextContent();

const pageText = textContent.items.map(item => item.str).join(" ");
console.log(pageText);
PDF.js is a Portable Document Format (PDF) library that is built with HTML5...

Each entry in textContent.items is a TextItem:

Field Meaning
str The text string of this fragment.
dir Text direction: "ltr", "rtl", or "ttb".
transform A 6-number matrix giving the fragment's position and scale on the page.
width, height Size of the fragment in device space.
fontName Internal font id (look it up in textContent.styles).
hasEOL true when this fragment is followed by a line break.

6.2 Extract text from the whole document

async function extractAllText(pdf) {
  let full = "";
  for (let n = 1; n <= pdf.numPages; n++) {
    const page = await pdf.getPage(n);
    const { items } = await page.getTextContent();
    full += items.map(i => i.str).join(" ") + "\n\n";
    page.cleanup();
  }
  return full;
}

6.3 Streaming text from huge pages

For pages with enormous amounts of text, consume the content as a stream instead of buffering it all:

const reader = page.streamTextContent().getReader();
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  process(value.items);
}

6.4 Reconstructing lines

Because a TextItem is a fragment, not a line, use hasEOL to rebuild line breaks:

const { items } = await page.getTextContent();
let line = "";
const lines = [];
for (const item of items) {
  line += item.str;
  if (item.hasEOL) { lines.push(line); line = ""; }
}
if (line) lines.push(line);

Tip

Pass { disableNormalization: true } to getTextContent if you need the raw, un-normalized characters (for example, to preserve specific ligatures or whitespace exactly as encoded). By default, whitespace is normalized to standard spaces.


7. Reading Document Information

A PDFDocumentProxy exposes rich metadata beyond the page images.

7.1 Metadata and document properties

const { info, metadata } = await pdf.getMetadata();
console.log(info.Title, info.Author, info.Producer);
if (metadata) {
  console.log(metadata.get("dc:title")); // XMP metadata, when present
}

7.2 Outline (bookmarks / table of contents)

const outline = await pdf.getOutline(); // array of OutlineNode, or null
function walk(nodes, depth = 0) {
  for (const node of nodes ?? []) {
    console.log("  ".repeat(depth) + node.title);
    if (node.items?.length) walk(node.items, depth + 1);
  }
}
walk(outline);

Each OutlineNode has title, bold, italic, color, a dest (where it jumps to), an optional url, and child items.

7.3 Page labels, layout, and open action

const labels = await pdf.getPageLabels();       // e.g. ["i", "ii", "1", "2", …] or null
const layout = await pdf.getPageLayout();        // e.g. "TwoColumnLeft"
const pageMode = await pdf.getPageMode();         // e.g. "UseOutlines"
const openAction = await pdf.getOpenAction();     // initial destination/action

7.4 Attachments and JavaScript

const attachments = await pdf.getAttachments();   // embedded files, or null
const jsActions = await pdf.getJSActions();         // document-level JS, or null

7.5 Permissions

const flags = await pdf.getPermissions(); // array of PermissionFlag values, or null
const canPrint = flags?.includes(pdfjsLib.PermissionFlag.PRINT);

Note

getPermissions() returns null when the PDF declares no restrictions (i.e. everything is allowed). The flags reflect the document author's stated intent; PDF.js surfaces them so you can choose to honor them.

7.6 Named destinations

const dest = await pdf.getDestination("chapter3"); // resolve a named link target
const pageIndex = await pdf.getPageIndex(dest[0]);  // 0-based page index

8.1 Reading a page's annotations

const annotations = await page.getAnnotations(); // { intent: "display" } by default
for (const a of annotations) {
  console.log(a.subtype, a.rect); // e.g. "Link", "Widget", "Text", …
}

Pass { intent: "print" } to fetch the annotations that would appear when printing, or { intent: "any" } for all of them.

8.2 Rendering an interactive layer

Rendering annotations as real, clickable HTML (links you can follow, form fields you can fill) is done with the AnnotationLayer class on top of the canvas. The bundled viewer wires this for you; for custom integrations, the building blocks are exported:

import { AnnotationLayer } from "pdfjs-dist/build/pdf.mjs";

You provide the page's annotation data, a viewport, a positioned <div>, and a link service. (For most apps, embedding the prebuilt viewer is far simpler than assembling these layers by hand.)

8.3 Filling and saving forms

User input into AcroForm fields is held in the document's annotation storage. To produce a new PDF that bakes in those values:

// After the user fills fields rendered with annotationMode ENABLE_STORAGE/forms:
const bytes = await pdf.saveDocument();   // Uint8Array of the modified PDF
download(bytes, "filled.pdf");

Important

saveDocument() serializes the current form/annotation state into a fresh PDF byte stream. It is the correct way to "export a filled form." Do not try to mutate the original input bytes yourself.

8.4 The annotation editor (highlight, draw, text, stamps, signatures)

PDF.js includes a full editor for adding free-text notes, ink drawings, highlights, image stamps, and signatures. The editor types are enumerated in AnnotationEditorType (FREETEXT, HIGHLIGHT, INK, STAMP, SIGNATURE, COMMENT, …). In practice you enable and drive the editor through the viewer's toolbar; the underlying AnnotationEditorUIManager and AnnotationEditorLayer are exported for advanced custom UIs.


9. Password-Protected PDFs

9.1 Supplying a known password

const pdf = await pdfjsLib.getDocument({ url: "secret.pdf", password: "letmein" }).promise;

9.2 Prompting the user when needed

If you don't know the password ahead of time, set onPassword on the loading task. PDF.js calls it when a password is required or when the one you gave was wrong.

const loadingTask = pdfjsLib.getDocument("secret.pdf");

loadingTask.onPassword = (updatePassword, reason) => {
  if (reason === pdfjsLib.PasswordResponses.NEED_PASSWORD) {
    updatePassword(prompt("This PDF is password-protected. Enter the password:"));
  } else if (reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
    updatePassword(prompt("Incorrect password. Try again:"));
  }
};

try {
  const pdf = await loadingTask.promise;
} catch (err) {
  if (err?.name === "PasswordException") {
    console.error("Could not unlock the document.");
  }
}
PasswordResponses value Meaning
NEED_PASSWORD (1) The document is encrypted and no password was supplied yet.
INCORRECT_PASSWORD (2) The supplied password was wrong; ask again.

10. Tracking Load Progress

Show a loading bar by listening to onProgress on the loading task.

const loadingTask = pdfjsLib.getDocument("big-report.pdf");

loadingTask.onProgress = ({ loaded, total }) => {
  const pct = total ? Math.round((loaded / total) * 100) : 0;
  console.log(`Loading… ${pct}%`);
};

const pdf = await loadingTask.promise;

The callback receives loaded (bytes so far) and total (total bytes, when the server reports it). If total is unknown, compute a spinner instead of a percentage.

Tip

For large remote PDFs, PDF.js fetches the file in ranges and renders early pages before the whole document arrives. This "progressive loading" is on by default when the server supports HTTP range requests. You can disable it with disableRange: true, disableStream: true, or disableAutoFetch: true (see Reference).


11. Using PDF.js in Node.js

PDF.js runs server-side for tasks like rendering pages to images, extracting text, or validating PDFs in a pipeline. There is no DOM in Node, so you render onto a canvas supplied by a native canvas library.

Note

In Node.js, Web Workers aren't available, so PDF.js automatically runs the engine on the main thread ("fake worker"). This is expected — no workerSrc setup is needed. Several defaults also flip in Node: disableFontFace is true, useSystemFonts and isOffscreenCanvasSupported default to false.

11.1 Extracting text in Node

import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
import { readFileSync } from "node:fs";

const data = new Uint8Array(readFileSync("input.pdf"));
const pdf = await getDocument({ data }).promise;

let text = "";
for (let n = 1; n <= pdf.numPages; n++) {
  const page = await pdf.getPage(n);
  const content = await page.getTextContent();
  text += content.items.map(i => i.str).join(" ") + "\n";
}
console.log(text);

11.2 Rendering a page to a PNG (the bundled example)

The repository ships a working example at examples/node/pdf2png/. It converts the first page of a PDF to a PNG using a native canvas backend.

# From a checkout of the repository:
npm install
gulp dist-install

cd examples/node/pdf2png
node pdf2png.mjs

The script loads a PDF, builds a viewport at a chosen scale, renders to a node-canvas, and writes the result with pngjs. Use it as a template for your own server-side rasterization.

Tip

When loading PDF.js in Node, import from the legacy/ build path (pdfjs-dist/legacy/build/pdf.mjs). The legacy build includes the compatibility shims needed outside evergreen browsers.


12. The Prebuilt Web Viewer

The web/ folder is a complete, production-quality PDF reader — the same UI Firefox uses. Serve the folder over HTTP and open web/viewer.html.

12.1 Opening a document

Pass the file through the URL hash:

web/viewer.html?file=/documents/manual.pdf

The file parameter must point to a same-origin URL (or a CORS-enabled one). You can also open a local file with the toolbar's Open button.

12.2 What the toolbar gives you

Control What it does
Previous / Next page Move one page at a time.
Page number box Jump directly to a page (or a named page label).
Zoom out / in / select Change zoom; presets include Automatic, Actual Size, Page Fit, and Page Width.
Find (search) Full-text search with highlight-all, match-case, whole-word, and match-diacritics options.
Sidebar toggle Show thumbnails, the document outline, attachments, or layers.
Presentation mode Full-screen, one-page-at-a-time reading.
Open file Load a PDF from disk.
Print Print via the browser, at the configured print resolution.
Download / Save Save the current document (including filled form data).
Rotate Rotate the view clockwise / counter-clockwise.
Tools menu Document properties, hand/select cursor tools, scroll & spread modes.
Editor tools Add text, draw (ink), highlight, image stamp, and signature annotations.

12.3 Keyboard shortcuts

Keys Action
← / →, Page Up / Page Down Previous / next page
Ctrl/Cmd + F Open find bar
Ctrl/Cmd + + / - Zoom in / out
Ctrl/Cmd + 0 Reset zoom to automatic
Ctrl/Cmd + P Print
Ctrl/Cmd + S Save / download
R / Shift + R Rotate clockwise / counter-clockwise
Ctrl/Cmd + Scroll Zoom with the mouse wheel

12.4 Deep-linking with hash parameters

The viewer reads several parameters from the URL hash to open at a specific location or state:

web/viewer.html?file=doc.pdf#page=12
web/viewer.html?file=doc.pdf#zoom=150
web/viewer.html?file=doc.pdf#nameddest=Chapter.4
web/viewer.html?file=doc.pdf#page=3&zoom=page-width
Hash parameter Effect
page=N Open at page N.
zoom=VALUE Set zoom — a percentage (150) or a named mode (page-fit, page-width, auto).
nameddest=NAME Jump to a named destination defined in the PDF.
pagemode=MODE Open with the sidebar showing thumbs, bookmarks, attachments, or none.
search=TEXT Pre-fill and run a search.

Tip

The viewer's default behavior (initial zoom, scroll/spread mode, sidebar, whether scripting is enabled, and dozens more) is controlled by viewer options. End users don't normally touch these; integrators set them via the preferences mechanism. The full catalog is documented in the Admin Guide → Configuration Reference.


13. Common Tasks (Recipes)

13.1 Render the first page as a thumbnail

async function thumbnail(url, maxWidth = 200) {
  const pdf = await pdfjsLib.getDocument(url).promise;
  const page = await pdf.getPage(1);
  const unscaled = page.getViewport({ scale: 1 });
  const scale = maxWidth / unscaled.width;
  const viewport = page.getViewport({ scale });

  const canvas = document.createElement("canvas");
  canvas.width = viewport.width;
  canvas.height = viewport.height;
  await page.render({ canvas, canvasContext: canvas.getContext("2d"), viewport }).promise;
  return canvas.toDataURL("image/png");
}

13.2 Build a simple page navigator

const pdf = await pdfjsLib.getDocument("doc.pdf").promise;
let current = 1;

async function go(n) {
  current = Math.min(Math.max(1, n), pdf.numPages);
  const page = await pdf.getPage(current);
  const viewport = page.getViewport({ scale: 1.3 });
  canvas.width = viewport.width;
  canvas.height = viewport.height;
  await page.render({ canvas, canvasContext: canvas.getContext("2d"), viewport }).promise;
  label.textContent = `Page ${current} / ${pdf.numPages}`;
}

prevBtn.addEventListener("click", () => go(current - 1));
nextBtn.addEventListener("click", () => go(current + 1));
go(1);

13.3 Search for text across a document

async function findPages(pdf, query) {
  const hits = [];
  const needle = query.toLowerCase();
  for (let n = 1; n <= pdf.numPages; n++) {
    const page = await pdf.getPage(n);
    const { items } = await page.getTextContent();
    const text = items.map(i => i.str).join(" ").toLowerCase();
    if (text.includes(needle)) hits.push(n);
    page.cleanup();
  }
  return hits; // page numbers containing the query
}

13.4 Export a filled form as a new PDF

// User has filled fields in the viewer / your annotation layer (storage-backed).
const bytes = await pdf.saveDocument();
const blob = new Blob([bytes], { type: "application/pdf" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "completed-form.pdf";
a.click();

13.5 Render for high-quality printing

Use the print intent and storage-backed annotations so form values appear:

const viewport = page.getViewport({ scale: 300 / 72 }); // ~300 DPI
await page.render({
  canvas, canvasContext: context, viewport,
  intent: "print",
  annotationMode: pdfjsLib.AnnotationMode.ENABLE_STORAGE,
}).promise;

13.6 Clean shutdown in a single-page app

const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
// … use the document …
await loadingTask.destroy(); // aborts requests and terminates the worker

14. Advanced Usage

14.1 Sharing one worker across many documents

By default each getDocument call creates and owns a worker. To load many PDFs efficiently, create one PDFWorker and reuse it:

const worker = new pdfjsLib.PDFWorker({ name: "shared" });
const a = await pdfjsLib.getDocument({ url: "a.pdf", worker }).promise;
const b = await pdfjsLib.getDocument({ url: "b.pdf", worker }).promise;
// …
worker.destroy(); // when finished with all of them

14.2 Optional content (layers / OCGs)

Some PDFs define toggleable layers (e.g. CAD drawings). Fetch their configuration and control visibility:

const ocConfig = await pdf.getOptionalContentConfig();
const groups = ocConfig.getGroups();
// Toggle a group, then re-render passing the config:
await page.render({
  canvas, canvasContext: context, viewport,
  optionalContentConfigPromise: Promise.resolve(ocConfig),
}).promise;

14.3 XFA forms

XFA is a legacy XML-based form technology. Rendering it is off by default; enable it with enableXfa: true:

const pdf = await pdfjsLib.getDocument({ url: "xfa-form.pdf", enableXfa: true }).promise;
if (pdf.isPureXfa) {
  const xfaHtml = await (await pdf.getPage(1)).getXfa();
  // Render the returned fake-DOM tree with XfaLayer.
}

14.4 Embedded PDF JavaScript

PDFs can carry their own scripts (field calculations, validations, dialogs). Running them requires the scripting sandbox (pdf.sandbox.mjs) backed by a QuickJS WebAssembly engine, and is enabled per-deployment. Check whether a document has scripts:

const hasScripts = await pdf.hasJSActions();

Caution

Executing a PDF's embedded JavaScript is a security-sensitive feature. PDF.js confines it to a sandboxed engine that cannot touch the network, the file system, or arbitrary browser APIs. Only enable scripting if you understand the implications. The bundled viewer enables it by default (except in certain builds); see the Admin Guide.

14.5 Image decoders only

If you only need to decode images (JBIG2, JPEG, JPEG 2000) and not full PDFs, a slimmer entry point is available:

import * as decoders from "pdfjs-dist/image_decoders/pdf.image_decoders.mjs";
// decoders.JpegImage, decoders.Jbig2Image, decoders.JpxImage

15. Troubleshooting

Error: Setting up fake worker

Cause. GlobalWorkerOptions.workerSrc was not set (or points to a file that can't be loaded), so PDF.js fell back to running the engine on the main thread.

Solution. Set the worker source before calling getDocument:

pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.min.mjs";

Prevention. Confirm the worker file is served and reachable at that URL (open it directly in the browser — you should get JavaScript, not a 404).


Error: The API version "X" does not match the Worker version "Y"

Cause. Your main library file and your worker file come from different PDF.js releases.

Solution. Serve pdf.mjs and pdf.worker.mjs from the same pdfjs-dist version. If using a CDN, pin both URLs to the same version number.

Prevention. Bundle both files from one installed copy rather than mixing a CDN worker with a local library (or vice versa).


Error: PasswordException

Cause. The PDF is encrypted and no password (or a wrong password) was provided.

Solution. Provide password in getDocument, or handle loadingTask.onPassword (see §9).


Error: InvalidPDFException / "Invalid PDF structure"

Cause. The bytes are not a valid PDF — truncated download, an HTML error page served instead of the file, or a non-PDF file.

Solution. Verify the URL returns the actual PDF with Content-Type: application/pdf. Check for redirects or login pages being returned instead of the file.

Prevention. When loading by URL, confirm the response status is 200 and the body starts with %PDF-.


Error: Worker was destroyed / Loading aborted / Transport destroyed

Cause. The document (or its loading task) was destroyed while an operation was still pending — common when navigating away or re-loading rapidly.

Solution. Await or cancel outstanding operations before calling destroy(). Guard render/text calls so they stop once the document is torn down.


Warning: RenderingCancelledException

Cause. A render was cancelled because a newer one started on the same canvas (e.g. fast zooming).

Solution. This is expected. Catch it and ignore it (see §5.5).


Error: Cannot use more than one PDFWorker per port.

Cause. Two PDFWorker instances were created from the same workerPort.

Solution. Create one PDFWorker for a given port and share it, or let getDocument manage its own worker.


Error: Invalid \workerSrc` type./Invalid `workerPort` type.`

Cause. GlobalWorkerOptions.workerSrc was set to a non-string, or workerPort to something that isn't a Worker.

Solution. Assign a string path to workerSrc, or a real Worker instance to workerPort.


Blank pages, missing glyphs, or "tofu" boxes

Cause. The PDF uses CJK text, ICC color, or non-embedded standard fonts, and the supporting asset folders aren't configured.

Solution. Set cMapUrl + cMapPacked: true, standardFontDataUrl, and iccUrl to folders served from your origin (see §2.2).


Blurry rendering on high-DPI screens

Cause. The canvas was sized in CSS pixels without accounting for devicePixelRatio.

Solution. Multiply the canvas backing size by the output scale and apply a matching transform (see §5.2).


A cross-origin PDF won't load

Cause. Same-origin policy / missing CORS headers on the remote server.

Solution. Serve the PDF from your own origin, enable CORS on the remote server, or proxy the request. For credentialed requests, also set withCredentials: true.


16. Frequently Asked Questions

Q: Does PDF.js need a server or any backend service? No. All parsing and rendering happen in the browser (or in Node). You only need a static HTTP server to deliver the PDF and the library files.

Q: Can it render any PDF perfectly? PDF.js aims for broad, faithful rendering and handles the vast majority of real-world documents, including forms, layers, and many font and image formats. Extremely unusual or malformed files may render imperfectly; PDF.js tries to recover gracefully rather than fail.

Q: How do I get selectable / copyable text over the rendered page? Render a text layer positioned over the canvas using the extracted getTextContent. The bundled viewer does this automatically; for custom UIs, the TextLayer building block is exported.

Q: Is it free for commercial use? Yes. PDF.js is licensed under Apache 2.0, which allows commercial use, modification, and redistribution.

Q: Why is there a separate worker file? PDF parsing and rendering are CPU-intensive. Running them on a Web Worker keeps your page responsive. That's why workerSrc must point at pdf.worker.mjs.

Q: What's the difference between the regular build and the legacy build? The legacy build adds compatibility shims for non-evergreen environments (including Node.js and older browsers). Use the default build for modern browsers and the legacy build elsewhere.

Q: Can I edit / annotate a PDF and save it? Yes. The annotation editor supports text notes, ink drawings, highlights, image stamps, and signatures. Use saveDocument() to export a new PDF that includes the edits and filled form fields.

Q: How do I display CJK (Chinese/Japanese/Korean) PDFs correctly? Provide the cmaps/ folder via cMapUrl (with cMapPacked: true) and, when needed, the standard fonts folder. Without these, some CJK glyphs may not appear.

Q: How do I make the viewer open at a specific page? Append #page=N to the viewer URL (e.g. viewer.html?file=doc.pdf#page=10). See §12.4.

Q: Can I run multiple PDFs at once without spawning many workers? Yes — create one PDFWorker and pass it to each getDocument call (see §14.1).

Q: How do I reduce memory usage with large documents? Call page.cleanup() after you finish with each page, destroy documents you no longer need, and let progressive loading fetch ranges on demand instead of disabling it.

Q: Does scrolling fast cause errors? You'll see RenderingCancelledException when a render is superseded. That's normal — catch and ignore it.

Q: Where do I configure the viewer's default zoom, theme, or behavior? Those are viewer options/preferences, covered in the Admin Guide → Configuration Reference.

Q: Can I extract a subset of pages into a new PDF? Yes — PDFDocumentProxy.extractPages() builds a new document from selected pages (with control over inclusion/exclusion ranges and ordering).


17. API Reference

17.1 getDocument(src) parameters

The most-used parameters, taken directly from the source. All are optional except that you must supply exactly one of url, data, or range.

Parameter Type Default Purpose
url string | URL — URL of the PDF to fetch.
data TypedArray | ArrayBuffer | number[] | string — In-memory PDF bytes. (Ownership transfers to the worker.)
range PDFDataRangeTransport — Custom on-demand data transport.
password string null Password for encrypted PDFs.
httpHeaders Object null Extra request headers (e.g. auth).
withCredentials boolean false Send cookies/credentials on cross-site requests.
rangeChunkSize number 65536 Bytes per range request.
worker PDFWorker — Reuse an existing worker.
verbosity number WARNINGS Logging level (VerbosityLevel).
docBaseUrl string null Base URL for recovering relative links.
cMapUrl string — Folder of predefined Adobe CMaps (CJK).
cMapPacked boolean true CMaps are binary-packed.
iccUrl string — Folder of ICC color profiles.
standardFontDataUrl string — Folder of the 14 standard fonts.
wasmUrl string — Folder of WebAssembly modules.
useSystemFonts boolean true (web) Fall back to system fonts for non-embedded fonts.
useWorkerFetch boolean true (web) Fetch CMaps/fonts/wasm inside the worker.
useWasm boolean true Use WebAssembly to speed up image decoding.
stopAtErrors boolean false Reject (instead of recover) on parse errors.
maxImageSize number -1 Max image pixels to render (-1 = no limit).
disableFontFace boolean false (web) Render fonts with the built-in path renderer instead of @font-face.
fontExtraProperties boolean false Export extra font data (debugging).
enableXfa boolean false Render XFA forms.
ownerDocument HTMLDocument current Document context for creating elements/fonts.
disableRange boolean false Disable HTTP range-request loading.
disableStream boolean false Disable streamed loading.
disableAutoFetch boolean false Don't pre-fetch beyond what's displayed.
pdfBug boolean false Enable debugging hooks.
enableHWA boolean false Enable hardware-accelerated rendering.

17.2 Key methods

PDFDocumentLoadingTask — returned by getDocument.

Member Description
promise Resolves to a PDFDocumentProxy.
onProgress(cb) Loading progress ({ loaded, total }).
onPassword(cb) Called when a password is required.
destroy() Abort requests and tear down the worker.

PDFDocumentProxy — the loaded document.

Member Description
numPages Total page count.
getPage(n) Get page n (1-based) → PDFPageProxy.
getMetadata() { info, metadata }.
getOutline() Bookmark tree, or null.
getPageLabels() Custom page labels, or null.
getAttachments() Embedded files, or null.
getPermissions() Permission flags, or null.
getOptionalContentConfig() Layer (OCG) configuration.
getDestination(id) / getDestinations() Resolve named link targets.
getPageIndex(ref) 0-based page index for a reference.
saveDocument() Serialize the document (with edits/form data).
extractPages(pageInfos) Build a new PDF from selected pages.
getData() Raw bytes of the document.
cleanup() Release cached resources.

PDFPageProxy — one page.

Member Description
pageNumber 1-based page number.
rotate Intrinsic clockwise rotation, in degrees.
view Page box [x1, y1, x2, y2] in PDF units.
getViewport({ scale, rotation }) Build a PageViewport.
render(params) Render to a canvas → RenderTask.
getTextContent(params) Extract text → { items, styles, lang }.
streamTextContent(params) Stream text content.
getAnnotations({ intent }) Annotation objects for the page.
getStructTree() Accessibility structure tree.
getOperatorList() Low-level drawing operator list.
cleanup() Release the page's resources.

RenderTask — returned by render().

Member Description
promise Resolves when rendering completes.
cancel(extraDelay?) Abort rendering (rejects with RenderingCancelledException).
onContinue Hook for incremental rendering.

17.3 Key classes

Class Purpose
GlobalWorkerOptions Set workerSrc (path) or workerPort (a Worker). Required setup.
PDFWorker A reusable worker; pass via getDocument({ worker }).
PDFDataRangeTransport Abstract base for supplying PDF bytes on demand (streaming).
AnnotationLayer Renders clickable links and form widgets as HTML.
TextLayer Renders a selectable text overlay.
AnnotationEditorLayer / AnnotationEditorUIManager Drive the annotation editor.

17.4 Enumerations

VerbosityLevel — logging: ERRORS (0), WARNINGS (1), INFOS (5).

PasswordResponses — NEED_PASSWORD (1), INCORRECT_PASSWORD (2).

AnnotationMode — DISABLE (0), ENABLE (1), ENABLE_FORMS (2), ENABLE_STORAGE (3).

AnnotationEditorType — DISABLE (-1), NONE (0), FREETEXT (3), HIGHLIGHT (9), STAMP (13), INK (15), SIGNATURE (101), COMMENT (102).

PermissionFlag — PRINT (0x04), MODIFY_CONTENTS (0x08), COPY (0x10), MODIFY_ANNOTATIONS (0x20), FILL_INTERACTIVE_FORMS (0x100), COPY_FOR_ACCESSIBILITY (0x200), ASSEMBLE (0x400), PRINT_HIGH_QUALITY (0x800).

AnnotationType (selection) — TEXT (1), LINK (2), FREETEXT (3), LINE (4), SQUARE (5), CIRCLE (6), HIGHLIGHT (9), UNDERLINE (10), STAMP (13), INK (15), POPUP (16), WIDGET (20) … (26 types total).

17.5 Exceptions you may catch

Exception (err.name) When it's thrown
PasswordException Encrypted PDF needs a (correct) password.
InvalidPDFException Data isn't a valid PDF.
MissingPDFException The PDF couldn't be fetched (e.g. 404).
ResponseException HTTP error while fetching.
RenderingCancelledException A render was cancelled — expected, ignore it.
AbortException An operation was aborted during teardown.
UnknownErrorException A catch-all for unexpected failures.