Library / SDK
ArtifexSoftware/mupdf.js avatar
ArtifexSoftware/mupdf.js

MuPDF.js puts the Artifex PDF engine inside a WebAssembly module

JavaScript bindings for MuPDF

610 stars50 forksUnknownAGPL-3.0

At a glance

What is it?
mupdf is the official JavaScript and TypeScript binding for the MuPDF C engine, packaged as a WebAssembly module that runs in Node.js, browsers, Bun and Deno without native builds. It covers rendering, extraction, annotation and redaction, at the cost of an AGPL licence and a package that publishes far less often than its repository moves.
Who is it for?
mupdf is the right pick when a service or a browser app has to render, extract, annotate or redact PDFs with the same engine used in many commercial PDF viewers, and when shipping a self-contained WebAssembly binary matters more than avoiding an AGPL obligation.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 114 days ago.
What is it written in?
GitHub does not report a main language for this repository.

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

Editorial analysis

The MuPDF C engine reached through WebAssembly

MuPDF.js exists because the PDF engine most people want is written in C. Artifex Software, the creators of MuPDF and Ghostscript, publish an official JavaScript and TypeScript library that wraps the C engine in WebAssembly, so the same rendering code can run in Node.js, Bun, Deno and modern browsers with no native dependencies and no platform-specific builds.

That packaging choice is the project's real contribution. A native addon means a per-platform binary per runtime, and browsers have no addon mechanism at all. The WebAssembly binary ships inside the npm package, so the install is a normal dependency add and the host runtime is whatever you already have.

The library is not only a renderer. The documentation lists page operations, document merging, metadata editing, outlines, form field values, password handling, annotation creation and redaction, which means the binding exposes the document model as well as the raster output. The bundled TypeScript definitions live at node_modules/mupdf/dist/mupdf.d.ts, and no separate @types package is needed.

One naming trap: the package on npm is `mupdf`, while an older community package named `mupdf-js` with a hyphen exists and is deprecated.

Installing mupdf and switching a CommonJS project to ESM

The install itself is one command.

bash
npm install mupdf

The package includes both the JavaScript wrapper and the WebAssembly binary, so there is no separate download or build step. Yarn and pnpm are covered in the documentation too, with the same package name.

The catch is module format. `mupdf` is ESM only, so a project that still uses require() needs to declare itself a module:

bash
npm pkg set type=module

For TypeScript, the documentation asks for a tsconfig.json with the Node module resolution set:

json
{
  "compilerOptions": {
    "module": "nodenext"
  }
}

That single flag matters more than it looks. A TypeScript codebase on an older module setting will either fail to resolve the package or emit code that cannot import an ESM-only dependency at runtime. This is a common reason a team evaluates mupdf and then drops it, so treat the module migration as part of the cost rather than as a footnote.

Rendering every page to PNG from Node.js

The quick start opens a document from a buffer, counts pages, and writes one PNG per page. The scale factor converts PDF points to a 96 DPI raster, and DeviceRGB is the colour space requested for the pixmap.

js
import * as fs from "fs"
import * as mupdf from "mupdf"

const doc = mupdf.Document.openDocument(fs.readFileSync("input.pdf"), "application/pdf")
const n = doc.countPages()

for (let i = 0; i < n; i++) {
  const page = doc.loadPage(i)
  const pixmap = page.toPixmap(
    mupdf.Matrix.scale(96 / 72, 96 / 72),  // 96 DPI
    mupdf.ColorSpace.DeviceRGB
  )
  fs.writeFileSync(`page${i + 1}.png`, pixmap.asPNG())

The longer version of the same example then calls pixmap.destroy() and page.destroy() inside the loop and doc.destroy() at the end. Notice the destroy calls. Every page and every pixmap carries a destroy method, and nothing in the documentation describes an automatic cleanup that would save you. This is manual lifetime management inherited from the C engine, and it is the part of the API that bites a long-running service. A loop that loads pages without releasing them holds native memory for the life of the process.

Also note the explicit "application/pdf" argument. The binding does not guess the format from the file extension.

A browser viewer needs a local server, not a file open

The same engine runs in the browser, importing the module directly from the installed package path and fetching the PDF as an array buffer. The page is rendered to a pixmap, encoded as PNG and dropped into an image element through an object URL.

html
<script type="module">
  import * as mupdf from "./node_modules/mupdf/dist/mupdf.js"

  const response = await fetch("./input.pdf")
  const buffer = await response.arrayBuffer()

  const doc = mupdf.Document.openDocument(buffer, "application/pdf")
  const page = doc.loadPage(0)
  const pixmap = page.toPixmap(
    mupdf.Matrix.scale(96 / 72, 96 / 72),
    mupdf.ColorSpace.DeviceRGB
  )

  const blob = new Blob([pixmap.asPNG()], { type: "image/png" })
  const img = document.createElement("img")
  img.src = URL.createObjectURL(blob)

The documentation is explicit that a local server is required, because of Web Workers and WebAssembly loading. Opening the file directly from disk will not work, which rules out the simplest way to try the library.

bash
npx http-server -o index.html

For teams that want a viewer rather than a snippet, the repository carries example directories for Angular, Electron, Next.js, React and Vue, plus simple-viewer and super-simple-viewer projects, and a tasks example.

Extraction and search return geometry, not plain strings

Text extraction has two modes. The simple one converts a page to structured text and prints it, and the structured one parses the JSON form and walks the blocks, lines and bounding boxes, which is what you want when you need coordinates for a highlight or for layout analysis.

js
import * as fs from "fs"
import * as mupdf from "mupdf"

const doc = mupdf.Document.openDocument(fs.readFileSync("input.pdf"), "application/pdf")

for (let i = 0; i < doc.countPages(); i++) {
  const page = doc.loadPage(i)
  const text = page.toStructuredText("preserve-whitespace").asText()
  console.log(`--- Page ${i + 1} ---\n${text}`)
  page.destroy()
}

doc.destroy()

Search returns match locations as Quad coordinate arrays, so a caller can draw its own highlight rectangles over the rendered page instead of relying on the library to render annotations.

js
import * as fs from "fs"
import * as mupdf from "mupdf"

const doc = mupdf.Document.openDocument(fs.readFileSync("input.pdf"), "application/pdf")

const page = doc.loadPage(0)
const hits = page.search("invoice number")  // returns array of Quad arrays
console.log(`Found ${hits.length} matches`)
page.destroy()

The "preserve-whitespace" and "preserve-spans" arguments select how much layout survives into the output. The documentation does not enumerate every mode the structured text options accept, so treat the flags shown here as a starting point rather than a complete reference.

AGPL-3.0 with a commercial route, and what that means for a service

The licence is the decision that deserves the most attention. The repository is AGPL-3.0, and the badge in the documentation shows the pair of AGPL and Commercial. That pairing is the usual shape for a library whose owners want network use to be a paid arrangement rather than a free one.

What this means in practice, without giving legal advice: AGPL-3.0 covers the network use case, so a service that offers the functionality of this library over a network brings the source obligation into play. If your product cannot carry that, the vendor's commercial licensing path is the route to discuss. If your project is a client side application that never shares the library, the exposure is different, but that is a question for your own counsel rather than for this article.

The alternative approach is to bind MuPDF natively yourself instead of using this wrapper. The wrapper exists precisely so that you can avoid native builds, so choosing native bindings means taking on per platform packaging for Node.js and losing browser support entirely. The other option named in the documentation, the `mupdf-js` community package, is explicitly deprecated, so it is a migration source and not a destination.

Release tags from 2024 against commits in 2026

The repository is not archived, and the last push was on 2026-07-01. The published release history is much quieter than that: v0.3.0 on 2024-08-27 and v0.2.3 on 2024-07-22 are the most recent releases listed. The source tree moves while the npm package sits.

That gap is the practical upgrade risk. A team installing `mupdf` from the registry gets the 2024 build, while the git master branch carries work from a later date. If a fix you need exists on master, there is no tag to install it from. The project also ships an INSTALL.md alongside the project README, and the documentation site at mupdfjs.readthedocs.io is described as live, so the gap between the docs and the published package is worth checking before you assume a feature you read about is in the release you install.

For a dependency that renders untrusted PDFs and can annotate and rewrite them, the licence and the release cadence deserve the same attention as the API surface.

Editorial conclusion

mupdf is the right pick when a service or a browser app has to render, extract, annotate or redact PDFs with the same engine used in many commercial PDF viewers, and when shipping a self-contained WebAssembly binary matters more than avoiding an AGPL obligation. It is a poor fit for a CommonJS codebase that cannot move to ESM, for a product that cannot carry an AGPL dependency without a commercial agreement, or for a team that installs from npm and expects new releases, since the last listed releases are v0.3.0 and v0.2.3 from 2024 while the last push was on 2026-07-01. Before adopting it, check that your bundler can load the WebAssembly payload, confirm whether the published mupdf package matches the version your deployment expects, and read the AGPL terms against your distribution model.

Frequently asked questions

Is the mupdf npm package the same as mupdf-js?

No. The documentation names mupdf as the official package and states that the older community package mupdf-js, with a hyphen, is deprecated. Use mupdf.

Does mupdf work in a CommonJS project?

Not without a change. The package is ESM only, so you must use import rather than require, and the documentation suggests running npm pkg set type=module if your project defaults to CommonJS.

Can mupdf run in a browser without a server?

The documentation says a local server is required for Web Workers and WebAssembly loading, and gives npx http-server -o index.html as the way to serve the example. Opening the HTML file directly from disk is not the supported path.

What licence applies to mupdf and what are the options?

The repository is licensed AGPL-3.0, and the documentation badge pairs that with a commercial option from the vendor. For a product distributed over a network the AGPL obligations are the part to evaluate, and the commercial route is the alternative to discuss.

Official sources

  1. ArtifexSoftware/mupdf.js on GitHub
  2. License: AGPL-3.0
  3. Project website
  4. README
  5. Releases
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/artifexsoftware-mupdf-js.svg)](https://hysenlabs.com/projects/artifexsoftware-mupdf-js)

Was this useful?