Frequently Asked Questions#
Overview
MuPDF.js is the official JavaScript/TypeScript binding for MuPDF from Artifex Software. It's powered by WebAssembly (WASM), which means the actual MuPDF C library is compiled to run in JavaScript environments.
This enables:
• Browser-side PDF processing: Render PDFs using HTML5 Canvas without server round-trips
• Node.js server processing: Headless PDF manipulation for backend services
• Cross-platform: Works anywhere JavaScript runs — Node, Bun, Deno, browsers
The API provides document loading, page rendering, text extraction, annotations, and PDF manipulation.
Document Operations:
• Open PDF files
• Merge, split, and rearrange pages
• Crop and rotate pages
• Save modified documents
Rendering:
• Render pages to image format
• Render to HTML5 Canvas in browsers
• Control resolution and zoom
Content Extraction:
• Extract text with position information
• Search for text across documents
• Get document structure and metadata
Annotations:
• Create highlights, notes, and drawings
• Modify existing annotations
• Support for redaction annotations
MuPDF.js advantages:
• Fast, accurate rendering
• Full PDF editing capabilities (annotations, merging, etc.)
• Better font handling and text extraction
PDF.js advantages:
• Pure JavaScript (no WASM dependency)
• Mozilla-backed, widely used
• Smaller bundle size
• More permissive license (Apache 2.0)
Choose MuPDF.js when you need high-fidelity rendering, editing capabilities, or advanced text extraction. Choose PDF.js for simpler viewing scenarios where bundle size matters.
Yes, there was a community package called mupdf-js (note the hyphen). That package is now deprecated in favor of the official mupdf package from Artifex.
// OLD (deprecated)
// npm install mupdf-js
import { createMuPdf } from "mupdf-js";
// NEW (official, recommended)
// npm install mupdf
import mupdf from "mupdf";
The official package is actively maintained by Artifex and has a more complete API.
Installation & Setup
npm install mupdf
Or with yarn:
yarn add mupdf
Or with pnpm:
pnpm add mupdf
The package includes the WebAssembly binary and TypeScript definitions.
MuPDF.js works in any environment that supports WebAssembly:
• Node.js: 14+ (recommended: 18+)
• Bun: Full support
• Deno: With npm compatibility
• Browsers: Chrome, Firefox, Safari, Edge (all modern versions)
The module is ESM-only, so use import syntax, not require().
MuPDF.js includes TypeScript definitions out of the box:
import mupdf from "mupdf";
import type { Document, Page, Pixmap } from "mupdf";
const doc: Document = mupdf.Document.openDocument(buffer, "application/pdf");
const page: Page = doc.loadPage(0);
const pixmap: Pixmap = page.toPixmap(
mupdf.Matrix.identity,
mupdf.ColorSpace.DeviceRGB,
false,
true
);
No additional @types package needed.
MuPDF.js works with modern frameworks. Key considerations:
Client-side rendering: Load the WASM module asynchronously
Server-side (Next.js): Use in API routes or with dynamic imports
// React component example
import { useEffect, useState, useRef } from 'react';
export default function PdfViewer({ pdfUrl }) {
const canvasRef = useRef(null);
useEffect(() => {
async function renderPdf() {
const mupdf = await import('mupdf');
const response = await fetch(pdfUrl);
const buffer = await response.arrayBuffer();
const doc = mupdf.Document.openDocument(
new Uint8Array(buffer),
"application/pdf"
);
const page = doc.loadPage(0);
// ... render to canvas
}
renderPdf();
}, [pdfUrl]);
return <canvas ref={canvasRef} />;
}
For examples see: Building Web apps.
Node.js Usage
import * as fs from "fs";
import mupdf from "mupdf";
// Read file as buffer
const buffer = fs.readFileSync("input.pdf");
// Open document
const doc = mupdf.Document.openDocument(buffer, "application/pdf");
console.log(`Pages: ${doc.countPages()}`);
// Always close when done
doc.destroy();
import * as fs from "fs";
import mupdf from "mupdf";
const buffer = fs.readFileSync("input.pdf");
const doc = mupdf.Document.openDocument(buffer, "application/pdf");
const page = doc.loadPage(0);
// Create pixmap at 2x zoom (144 DPI)
const matrix = mupdf.Matrix.scale(2, 2);
const pixmap = page.toPixmap(
matrix,
mupdf.ColorSpace.DeviceRGB,
false, // no alpha
true // include annotations
);
// Save as PNG
const pngData = pixmap.asPNG();
fs.writeFileSync("page1.png", pngData);
// Cleanup
pixmap.destroy();
page.destroy();
doc.destroy();
MuPDF.js uses WebAssembly memory which isn't automatically garbage collected. Always call destroy() on objects when done:
const doc = mupdf.Document.openDocument(buffer, "application/pdf");
try {
const page = doc.loadPage(0);
try {
const pixmap = page.toPixmap(/*...*/);
try {
// Use pixmap...
const png = pixmap.asPNG();
} finally {
pixmap.destroy();
}
} finally {
page.destroy();
}
} finally {
doc.destroy();
}
For high-throughput servers, monitor memory usage and implement proper cleanup in error handlers.
Browser Usage
<canvas id="pdfCanvas"></canvas>
<script type="module">
import mupdf from "mupdf";
async function renderPdf(url) {
// Fetch PDF
const response = await fetch(url);
const buffer = await response.arrayBuffer();
// Open document
const doc = mupdf.Document.openDocument(
new Uint8Array(buffer),
"application/pdf"
);
const page = doc.loadPage(0);
const [, , width, height] = page.getBounds();
// Create pixmap
const scale = window.devicePixelRatio || 1;
const matrix = mupdf.Matrix.scale(scale, scale);
const pixmap = page.toPixmap(matrix, mupdf.ColorSpace.DeviceRGB, false, true);
// Draw to canvas
const canvas = document.getElementById("pdfCanvas");
canvas.width = width * scale;
canvas.height = height * scale;
canvas.style.width = width + "px";
canvas.style.height = height + "px";
const ctx = canvas.getContext("2d");
const imageData = new ImageData(
new Uint8ClampedArray(pixmap.getPixels()),
pixmap.getWidth(),
pixmap.getHeight()
);
ctx.putImageData(imageData, 0, 0);
// Cleanup
pixmap.destroy();
page.destroy();
doc.destroy();
}
renderPdf("/sample.pdf");
</script>
<input type="file" id="fileInput" accept=".pdf" />
<script type="module">
import mupdf from "mupdf";
document.getElementById("fileInput").addEventListener("change", async (e) => {
const file = e.target.files[0];
if (!file) return;
// Read file as ArrayBuffer
const buffer = await file.arrayBuffer();
// Open with MuPDF
const doc = mupdf.Document.openDocument(
new Uint8Array(buffer),
"application/pdf"
);
console.log(`Loaded: ${file.name}, ${doc.countPages()} pages`);
// Process document...
});
</script>
let doc = null;
let currentPage = 0;
async function loadDocument(buffer) {
doc = mupdf.Document.openDocument(new Uint8Array(buffer), "application/pdf");
currentPage = 0;
renderCurrentPage();
updateUI();
}
function renderCurrentPage() {
const page = doc.loadPage(currentPage);
// ... render to canvas
page.destroy();
}
function nextPage() {
if (currentPage < doc.countPages() - 1) {
currentPage++;
renderCurrentPage();
updateUI();
}
}
function prevPage() {
if (currentPage > 0) {
currentPage--;
renderCurrentPage();
updateUI();
}
}
function goToPage(num) {
if (num >= 0 && num < doc.countPages()) {
currentPage = num;
renderCurrentPage();
updateUI();
}
}
function updateUI() {
document.getElementById("pageInfo").textContent =
`Page ${currentPage + 1} of ${doc.countPages()}`;
}
The MuPDF.js package includes:
• JavaScript wrapper: ~50KB (minified)
• WebAssembly binary: ~8-12MB
The WASM binary is loaded asynchronously and can be cached by browsers. For production:
• Serve WASM with proper caching headers
• Consider lazy-loading MuPDF only when needed
• Use a CDN for the WASM file
Documents & Pages
const doc = mupdf.Document.openDocument(buffer, "application/pdf");
// Page count
console.log(`Pages: ${doc.countPages()}`);
// Metadata
console.log(`Title: ${doc.getMetaData("info:Title")}`);
console.log(`Author: ${doc.getMetaData("info:Author")}`);
console.log(`Subject: ${doc.getMetaData("info:Subject")}`);
console.log(`Creator: ${doc.getMetaData("info:Creator")}`);
console.log(`Producer: ${doc.getMetaData("info:Producer")}`);
console.log(`CreationDate: ${doc.getMetaData("info:CreationDate")}`);
// Check document type
console.log(`Is PDF: ${doc.isPDF()}`);
// Get outline (bookmarks/TOC)
const outline = doc.loadOutline();
if (outline) {
function printOutline(items, indent = 0) {
for (const item of items) {
console.log(" ".repeat(indent) + item.title);
if (item.down) printOutline(item.down, indent + 2);
}
}
printOutline(outline);
}
const page = doc.loadPage(0);
// Default is 72 DPI
// Scale factor = desired DPI / 72
// 150 DPI
const matrix150 = mupdf.Matrix.scale(150/72, 150/72);
// 300 DPI
const matrix300 = mupdf.Matrix.scale(300/72, 300/72);
// Create pixmap at specific DPI
const pixmap = page.toPixmap(
matrix300,
mupdf.ColorSpace.DeviceRGB,
false, // alpha
true // annotations
);
console.log(`Output size: ${pixmap.getWidth()} x ${pixmap.getHeight()} pixels`);
Text Extraction & Search
const page = doc.loadPage(0);
// Get structured text
const stext = page.toStructuredText();
// Extract as plain text
const text = stext.asText();
console.log(text);
// Or iterate through blocks, lines, characters
for (const block of stext.getBlocks()) {
if (block.type === "text") {
for (const line of block.lines) {
console.log(`Line at y=${line.bbox[1]}: ${line.text}`);
}
}
}
stext.destroy();
page.destroy();
const page = doc.loadPage(0);
const stext = page.toStructuredText();
// Search for text, returns array of quads (quadrilaterals)
const hits = stext.search("search term");
console.log(`Found ${hits.length} matches`);
for (const quad of hits) {
// quad contains 4 points defining the match location
// [x0,y0, x1,y1, x2,y2, x3,y3] - clockwise from top-left
console.log(`Match at: ${quad}`);
}
// Use quads to highlight matches
// (see Annotations section)
stext.destroy();
page.destroy();
const doc = mupdf.Document.openDocument(buffer, "application/pdf");
let fullText = "";
for (let i = 0; i < doc.countPages(); i++) {
const page = doc.loadPage(i);
const stext = page.toStructuredText();
fullText += `\n--- Page ${i + 1} ---\n`;
fullText += stext.asText();
stext.destroy();
page.destroy();
}
console.log(fullText);
doc.destroy();
Annotations
const doc = mupdf.Document.openDocument(buffer, "application/pdf");
const pdfDoc = doc.asPDF();
const page = pdfDoc.loadPage(0);
// Search for text to highlight
const stext = page.toStructuredText();
const quads = stext.search("important text");
if (quads.length > 0) {
// Create highlight annotation
const annot = page.createAnnotation("Highlight");
annot.setQuadPoints(quads);
annot.setColor([1, 1, 0]); // Yellow
annot.update();
}
// Save document
const outputBuffer = pdfDoc.saveToBuffer("incremental");
fs.writeFileSync("highlighted.pdf", outputBuffer);
stext.destroy();
page.destroy();
doc.destroy();
MuPDF.js supports creating these annotation types:
• Text markup: Highlight, Underline, StrikeOut, Squiggly
• Text: Text (sticky note), FreeText
• Drawing: Line, Square, Circle, Polygon, PolyLine, Ink
• Other: Stamp, FileAttachment, Redact
// Examples
const highlight = page.createAnnotation("Highlight");
const note = page.createAnnotation("Text");
const freetext = page.createAnnotation("FreeText");
const line = page.createAnnotation("Line");
const redact = page.createAnnotation("Redact");
page = pdfDoc.loadPage(0);
const annot = page.createAnnotation("Text");
annot.setRect([100, 100, 120, 120]); // Icon position
annot.setContents("This is a note comment");
annot.setColor([1, 1, 0]); // Yellow icon
annot.update();
const outputBuffer = pdfDoc.saveToBuffer("incremental");
fs.writeFileSync("with_note.pdf", outputBuffer);
PDF Editing
const page = pdfDoc.loadPage(0);
const annot = page.createAnnotation("FreeText");
annot.setRect([100, 100, 300, 150]);
annot.setContents("This is editable text");
annot.setDefaultAppearance("Helv", 12, [0, 0, 0]); // Font, size, color
annot.update();
pdfDoc.saveToBuffer("incremental");
const page = pdfDoc.loadPage(0);
// Create redaction annotation
const redact = page.createAnnotation("Redact");
redact.setRect([100, 200, 300, 220]); // Area to redact
redact.update();
// Apply all redactions (permanently removes content)
page.applyRedactions();
// Save - redacted content is permanently removed
const outputBuffer = pdfDoc.saveToBuffer("");
fs.writeFileSync("redacted.pdf", outputBuffer);
applyRedactions() permanently removes content. The original data cannot be recovered.Licensing
MuPDF.js is available under two licenses:
AGPL v3: Free for open-source projects. If you distribute software using MuPDF.js or provide it as a network service, you must release your source code under AGPL.
Commercial License: For proprietary applications, SaaS products, or when you can't comply with AGPL. Contact Artifex for pricing.
The licensing applies to both the JavaScript wrapper and the underlying MuPDF WASM binary.