This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /blog/pdfjs-server-side-text-extraction.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. How to extract text from PDFs server-side with PDF.js in Node.js

Table of contents

    Use pdfjs-dist in Node.js to extract text from PDFs without a browser. This tutorial covers basic text extraction, the TextContent item structure, parallel page processing, and CMap/font configuration.
    How to extract text from PDFs server-side with PDF.js in Node.js
    TL;DR

    PDF.js’s pdfjs-dist package runs in Node.js and can extract structured text from PDFs without rendering. The setup has four moving parts:

    • Load the document with getDocument({ data }), passing a Uint8Array from your Buffer.
    • Resolve standardFontDataUrl and cMapUrl from the pdfjs-dist package on disk.
    • Walk pages and call page.getTextContent() — filter for items with "str" in item.
    • Skip the worker configuration (no-op in Node.js) and polyfill Promise.withResolvers if your Node version lacks it.

    For parallel processing, wrap each page in a promise and Promise.all. If you’d rather skip the path-resolution dance, Nutrient Web SDK exposes textLinesForPageIndex in one call and Document Engine handles batch extraction over REST (representational state transfer).

    PDF.js isn’t just for browsers. You can use pdfjs-dist in Node.js to extract text from PDFs without any rendering. This is useful for search indexing and content analysis.

    Install

    Install the pdfjs-dist package:

    Terminal window
    npm install pdfjs-dist

    Basic text extraction

    The following example loads a PDF buffer, walks each page, and returns an array of per-page text strings:

    import { getDocument, PDFDocumentProxy } from "pdfjs-dist";
    import path from "path";
    import { createRequire } from "node:module";
    // Resolve paths for CMap and font data from the pdfjs-dist package.
    const require = createRequire(import.meta.url);
    const pdfjsDistPath = require.resolve("pdfjs-dist");
    const fontDataPath = path.join(path.resolve(pdfjsDistPath, "../../standard_fonts"), "/");
    const cmapPath = path.join(path.resolve(pdfjsDistPath, "../../cmaps"), "/");
    async function extractText(pdfBuffer) {
    const uint8Array = new Uint8Array(pdfBuffer);
    const pdf = await getDocument({
    data: uint8Array,
    standardFontDataUrl: fontDataPath,
    cMapUrl: cmapPath,
    }).promise;
    const pageTexts = [];
    for (let i = 1; i <= pdf.numPages; i++) {
    const page = await pdf.getPage(i);
    const textContent = await page.getTextContent();
    const pageText = textContent.items.reduce((text, item) => {
    if ("str" in item && "hasEOL" in item) {
    return text + item.str + (item.hasEOL ? "\n" : "");
    }
    return text;
    }, "");
    pageTexts.push(pageText);
    }
    return pageTexts;
    }

    Understanding TextContent items

    page.getTextContent() returns an object with an items array. Each item has:

    {
    str: "Hello world", // The text string.
    hasEOL: true, // Whether it's followed by a line break.
    dir: "ltr", // Text direction.
    width: 120.5, // Width in PDF points.
    height: 12, // Height in PDF points.
    transform: [12, 0, 0, 12, 72, 700], // Position/transform matrix.
    }

    Filter for items with str and hasEOL — some items are positioning markers without text.

    Parallel page processing

    For large PDFs, extract pages in parallel. The snippet below uses the pdf document from the basic example above:

    const textPromises = [];
    for (let i = 1; i <= pdf.numPages; i++) {
    textPromises.push(
    pdf.getPage(i)
    .then((page) => page.getTextContent())
    .then((textContent) =>
    textContent.items.reduce((text, item) => {
    if ("str" in item && "hasEOL" in item) {
    return text + item.str + (item.hasEOL ? "\n" : "");
    }
    return text;
    }, ""),
    ),
    );
    }
    const allTexts = await Promise.all(textPromises);

    Configuration notes

    PDF.js requires three adjustments when running in Node.js that differ from a browser setup.

    CMap and font paths

    In Node.js, you need to resolve these paths from the pdfjs-dist package itself:

    const pdfjsDistPath = require.resolve("pdfjs-dist");
    const fontDataPath = path.join(path.resolve(pdfjsDistPath, "../../standard_fonts"), "/");
    const cmapPath = path.join(path.resolve(pdfjsDistPath, "../../cmaps"), "/");

    These are directory paths to the bundled CMap and font files — required for proper text extraction from Chinese, Japanese, and Korean (CJK) and non-standard fonts.

    No worker needed

    In Node.js, you don’t need to configure GlobalWorkerOptions.workerSrc. PDF.js falls back to running in the main thread, which is fine for server-side batch processing.

    Polyfill

    If your Node.js version doesn’t support Promise.withResolvers, add a polyfill:

    import "core-js/proposals/promise-with-resolvers";

    Key points

    • pdfjs-dist works in Node.js without a browser or canvas.
    • getDocument({ data }) accepts Uint8Array from a Buffer.
    • page.getTextContent() gives you structured text with position metadata.
    • Filter text items for "str" in item to skip non-text markers.
    • No worker configuration is needed for server-side use.
    • Always configure CMap and standard font paths for proper text extraction.
    • Text extraction is the foundation for search indexing and content analysis.

    FAQ

    Can I really run PDF.js in Node.js without a browser?

    Yes. pdfjs-dist works in Node.js for text extraction, metadata reading, and structural inspection. The catch is that rendering (canvas painting) requires either a browser environment or an emulated canvas package, so use pdf-lib, a headless browser, or a server-side renderer for that. Text extraction itself has no rendering requirement.

    Why do I need to resolve CMap and font paths manually in Node?

    In the browser, you typically point cMapUrl and standardFontDataUrl at a content delivery network (CDN). In Node.js, there’s no URL to fetch from, so you have to resolve the bundled cmaps and standard_fonts directories from inside the pdfjs-dist package. Without them, text extraction from PDFs with CJK or embedded standard fonts will return blanks or mojibake.

    What is Promise.withResolvers and why do I need to polyfill it?

    Promise.withResolvers is a recent JavaScript API (Stage 4, available in Node 22+) that PDF.js uses internally. On older Node.js versions, you’ll get a ReferenceError at import time. The core-js/proposals/promise-with-resolvers polyfill adds it. Node 22+ users can skip the polyfill.

    Why are some textContent.items missing the str field?

    getTextContent() returns a mixed array: Most items are text fragments with a str field, but some are positioning markers used internally to recreate the page’s reading order. Filter with "str" in item to skip them. The hasEOL flag tells you when an item should be followed by a line break in your assembled output.

    Do I need to call worker.destroy() or loadingTask.destroy() after extraction?

    Yes, for long-running processes. PDF.js holds the parsed document tree in memory, and on a busy server, you’ll leak across requests if you don’t release it. Call await loadingTask.destroy() (or pdf.destroy()) when you’re done with each PDF. For short-lived scripts, process exit cleans up automatically.

    How do Nutrient Web SDK and Document Engine compare?

    Nutrient Web SDK exposes instance.textLinesForPageIndex(pageIndex) for client-side extraction with one call — no CMap setup, no item filtering. For server-side batch jobs, Document Engine provides a REST API for text extraction, OCR, redaction, and content analysis without bundling PDF.js into your service.

    How Nutrient Web SDK handles this

    No Node.js PDF.js setup, no CMap path resolution, and no polyfills are required. Nutrient Web SDK extracts text from any page with a single API call:

    // Extract text from a page — one line.
    const textLines = await instance.textLinesForPageIndex(0);
    console.log(textLines.map((line) => line.contents).join("\n"));

    For server-side processing, Nutrient Document Engine provides text extraction, optical character recognition (OCR), and content analysis through a REST API, with no extra binary dependencies on the host.


    See Nutrient Web SDK for an alternative to wiring up PDF.js in Node. The text extraction and OCR guides cover the API in depth. Follow the migration guide to switch, or talk to Sales about your requirements.

    Austin Nguyen

    Austin Nguyen

    AI Engineer

    When Austin isn’t pulling all-nighters to build new features, he enjoys watching science videos on YouTube and cooking.

    Explore related topics

    Try for free Ready to get started?