Frequently Asked Questions#

Overview

QWhat is MuPDF.js and how does it work?+

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.

QWhat can MuPDF.js do?+

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

QHow does MuPDF.js compare to PDF.js?+

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.

QIs there an older "mupdf-js" package? What's the difference?+

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

QHow do I install MuPDF.js?+
npm install mupdf

Or with yarn:

yarn add mupdf

Or with pnpm:

pnpm add mupdf

The package includes the WebAssembly binary and TypeScript definitions.

QWhat JavaScript environments are supported?+

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().

QHow do I use MuPDF.js with TypeScript?+

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.

QHow do I use MuPDF.js with frameworks like React, Vue, or Next.js?+

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

QHow do I open a PDF file in Node.js?+

    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();
QHow do I render a PDF page to a PNG file?+

    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();
QHow do I build a REST API for PDF processing?+
QHow do I handle memory management in Node.js?+

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

QHow do I render a PDF to an HTML5 Canvas?+

    <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>
QHow do I handle user-uploaded PDF files?+

    <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>
QHow do I build a page navigation system?+

    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()}`;
    }
QWhat's the bundle size of MuPDF.js?+

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

Tip: The WASM binary compresses well (gzip/brotli reduces it to ~3-4MB over the wire).

Documents & Pages

QHow do I get document information and metadata?+

    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);
    }
QHow do I control rendering resolution/DPI?+

    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

QHow do I extract text from a page?+

    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();
QHow do I search for text in a document?+

    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();
QHow do I extract text from all pages?+

    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

QHow do I add a highlight annotation?+

    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();
QWhat annotation types can I create?+

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");
QHow do I add a text note (sticky note)?+

    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

QHow do I add a FreeText annotation (text box)?+

    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");
QHow do I apply redactions?+

    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);
Warning: applyRedactions() permanently removes content. The original data cannot be recovered.

Licensing

QWhat license is MuPDF.js under?+

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.

Contact Artifex for more details.