Open-source project
mozilla/pdf.js avatar
mozilla/pdf.js

PDF.js: one file in your page, a worker behind it

GitHub describes it as PDF Reader in JavaScript. The repository metadata lists JavaScript as its primary language. The metadata lists the Apache-2.0 license. This article stays within the project description and details documented in the GitHub repository README.

53,957 stars10,689 forksJavaScriptApache-2.0

At a glance

What is it?
PDF.js renders PDFs in the browser from JavaScript, but its build model is unusual: a production build emits two scripts, you include only one, and the browsers you target decide which build you compile. This walks the build, the coverage tooling, and the parts the docs leave undocumented.
Who is it for?
PDF.js is worth integrating if you need rendering you control, and you should budget for a build step rather than a dropped-in file. Skip it if you need an unbuilt script tag, a single bundle that covers old and new browsers, or evidence about how your own page exercises the library.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Only pdf.js goes in your page, and the worker arrives on its own

A production build of PDF.js emits two scripts into build/generic/build/: pdf.js and pdf.worker.js. Both are needed, but only pdf.js has to be included in a page, because pdf.js loads the worker itself. That one rule decides the shape of most integrations. You do not add a script tag for the worker, you do not point anything at its path, and you do not bundle it as a separate entry of your own. The pair also travels together on the way out, since the files are large and should be minified for production. The legacy build places the same two files in build/generic-legacy/build/ instead, so the build target changes where you look for output, not how many files you ship. If you are budgeting a deploy, the count to plan around is two scripts, and their size is called out as a concern rather than left implicit. The two-file rule also decides how you cache them, since a stale pdf.js paired with a fresh worker is a mismatch the build never warns you about.

Modern and legacy are separate builds, not one bundle with a fallback

There are two build targets, and the split exists because the modern one assumes native support for the latest JavaScript features. The commands are short:

code
$ npx gulp generic
$ npx gulp generic-legacy

That assumption is stated plainly for the online demo, which is offered as a modern browsers version at one address and an older browsers version at another. The practical consequence for an integrator is that there is no single bundle with a fallback layer. Compile generic and serve it to a browser lacking the newest language features, and nothing in the output steps in behind them. Compile generic-legacy to be cautious and you have chosen the older output for every visitor, not a subset. The decision is made at build time, which means it lands in your build configuration rather than in a feature check at runtime, and a visitor on the wrong side of it has no way to work around it.

Every path to a local copy begins with a clone and ends at a build

Getting the source and getting a usable file are two different steps, and the gap between them is where most surprises live. The documented route is to clone the repository, move into the directory, install Node.js, and install dependencies:

code
$ git clone https://github.com/mozilla/pdf.js.git
$ cd pdf.js
$ npm install
$ npx gulp server

A local web server is required to view anything, because some browsers do not allow opening PDF files using a file:// URL. Once the server is up, the viewer is at http://localhost:8888/web/viewer.html, and the bundled test documents are browsable at http://localhost:8888/test/pdfs/?frame. The consequence for a reader is a toolchain dependency in exchange for source: you are running a build system, and a deployment that serves the result over a file:// path will not behave the way the local server does. The instructions also note that this assumes the latest version of Mozilla Firefox.

Firefox and Chrome carry the viewer, not the API you would call

PDF.js reaches end users through channels that are not the embeddable library. Firefox 19 and later ship PDF.js built in, and Chrome has an official extension in the Chrome Web Store that a separate maintainer, @Rob--W, keeps current. A third route builds your own: npx gulp chromium produces an unpacked extension that you load from build/chromium through the browser's Tools > Extension menu. Every one of these is a distribution of the viewer, and conflating them with the library is the easiest way to misread the project. What you call from your own page comes out of the generic build, while what a Firefox user gets on version 19 or later is a browser feature and what the Chrome extension does is decided by its own maintainer. If your requirement is to control rendering inside your page, none of the three channels supplies that.

Coverage instruments the bundle, and only when a test task asks for it

When coverage is enabled, the build instruments the bundled code with babel-plugin-istanbul, which adds counters recording every line, branch, and function that runs. The two test environments collect differently. In the browser, the instrumented code fills a global window.__coverage__ object, and the test runner collects it from each browser session, merges the results, and writes the report. The Node-based unit tests write raw data to build/tmp/unittestcli-coverage.json and turn it into a report afterwards. The flag is opt-in per task:

code
$ npx gulp unittest --coverage           # browser unit tests
$ npx gulp unittestcli --coverage        # Node unit tests
$ npx gulp integrationtest --coverage    # Puppeteer integration tests
$ npx gulp botbrowsertest --coverage     # reference tests

Output lands in build/coverage in the info format by default, producing an lcov.info file. The limitation to keep in view is scope: these numbers describe PDF.js under its own test tasks, and they say nothing about how your page exercises the library.

coverage_search answers which reference tests touched one line

The tool answers a narrower question than the report does. Instead of a percentage it lists the reference tests that exercised a specific source line or function, addressed with a --code option that takes a file and a line or a name:

code
$ npx gulp coverage_search --code="canvas.js::205"
$ npx gulp coverage_search --code="canvas.js::drawImageAtIntegerCoords"

The same option narrows which tests actually run, which is how you regenerate reference images for just the affected area:

code
$ npx gulp browsertest --code="canvas.js::205"
$ npx gulp makeref --code="canvas.js::205"

Passing --no-download reuses the locally cached index without contacting the network, and the index can also be built and queried on your own machine, the same way the CI job that publishes it does:

code
$ npx gulp botbrowsertest --coverage-per-test
$ npx gulp coverage_search --code="canvas.js::205" \
    --index=build/coverage/per-test-index.json --no-download

Behind it sits a per-test index, rebuilt on every push to master and published to a separate pdf.js.refs repository, downloaded on demand, cached locally, and re-downloaded only when it has changed, so no local coverage build is required to query it. One consequence follows from that design: the index describes master as pushed, not the checkout in front of you.

The internal structure debugger is a standalone page, not a shipped artifact

The internal structure of a PDF document can be browsed at https://mozilla.github.io/pdf.js/internal-viewer/web/debugger.html, which is a different address from the viewer at https://mozilla.github.io/pdf.js/web/viewer.html and from the older browsers viewer at https://mozilla.github.io/pdf.js/legacy/web/viewer.html. Being a standalone page, it is not something the generic build emits for you to embed, and the build description does not fold it into the output directory alongside pdf.js and pdf.worker.js. For a reader this matters in a specific way: the tool that explains a malformed document is not the artifact you ship, so treating the debugger as part of your integration means assuming a capability the build does not hand you. The example directories in the tree, covering node, webpack, components, mobile-viewer, text-only, image_decoders, and learning, are likewise present in the repository without being walked through, which leaves their setup to the source itself. Alongside them the root carries src, web, test, extensions, l10n, docs, external, gulpfile.mjs, and pdfjs.config, so the viewer, the tests, the browser extensions, and the translation bundles all sit in one tree rather than in separate packages.

Editorial conclusion

PDF.js is worth integrating if you need rendering you control, and you should budget for a build step rather than a dropped-in file. Skip it if you need an unbuilt script tag, a single bundle that covers old and new browsers, or evidence about how your own page exercises the library. Before you commit, check the last push on master against the tagged versions, and decide early whether you are compiling generic or generic-legacy, because that choice is baked at build time.

Frequently asked questions

What is PDF JS used for?

PDF.js is a Portable Document Format viewer built with HTML5, and the stated goal is a general-purpose, web standards-based platform for parsing and rendering PDFs. It is community-driven and supported by Mozilla.

Is PDF JS free?

The repository is licensed under Apache-2.0, so it is free to use, modify, and redistribute under those terms. The project is community-driven and looks for more contributors.

Where can I download the PDF.js file?

For the library itself, the documented route is to clone https://github.com/mozilla/pdf.js.git, run npm install, and build with npx gulp generic, which produces pdf.js and pdf.worker.js. An online demo is available at https://mozilla.github.io/pdf.js/web/viewer.html, but that is the viewer, not a library archive.

how to use pdf.js in html

Include pdf.js in the page and let it load the worker itself. Both files are needed, but only pdf.js needs to be included, since pdf.worker.js is loaded by pdf.js. Some browsers do not allow opening PDF files over a file:// URL, so serve the page over a local server.

what is pdf js viewer

The generic viewer is what npx gulp generic builds alongside the two production scripts. Two demos exist online: a modern browsers version at https://mozilla.github.io/pdf.js/web/viewer.html and an older browsers version at https://mozilla.github.io/pdf.js/legacy/web/viewer.html, since the modern build assumes native support for the latest JavaScript features.

how to install pdf js

The documented setup is a local copy from git, Node.js installed, then npm install for all dependencies, then npx gulp server to start a local web server. The viewer is then reached at http://localhost:8888/web/viewer.html. The instructions assume the latest version of Mozilla Firefox.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mozilla-pdf-js.svg)](https://hysenlabs.com/projects/mozilla-pdf-js)