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
- Quick Start
- Installation
- Core Concepts (read this first)
- Loading a PDF Document
- Rendering Pages to a Canvas
- Extracting Text
- Reading Document Information
- Working with Annotations, Links, and Forms
- Password-Protected PDFs
- Tracking Load Progress
- Using PDF.js in Node.js
- The Prebuilt Web Viewer
- Common Tasks (Recipes)
- Advanced Usage
- Troubleshooting
- Frequently Asked Questions
- 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.workerSrcbefore callinggetDocument. 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 logsWarning: 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 youimport).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.mjsare minified for production. During development, use the non-minifiedpdf.mjs/pdf.worker.mjsso 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
TypedArrayasdata, 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
OutputScalehelper (pdfjsLib.OutputScale) and aPixelsPerInchconstant 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 }togetTextContentif 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()returnsnullwhen 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. Working with Annotations, Links, and Forms
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, ordisableAutoFetch: 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
workerSrcsetup is needed. Several defaults also flip in Node:disableFontFaceistrue,useSystemFontsandisOffscreenCanvasSupporteddefault tofalse.
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 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 |
|
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. |