How to extract text from a PDF in JavaScript: PDF.js and Nutrient (2026)
Table of contents
Nutrient Web SDK renders, annotates, and fills forms client side. Try the live demo — no signup required.
To extract PDF text in JavaScript, load a document with PDF.js and call getTextContent() for each page, or use Nutrient Web SDK’s textLinesForPageIndex() to retrieve text grouped into lines. Both examples below process all pages and preserve page separators. They extract an existing text layer; image-only pages need optical character recognition (OCR) first.
PDF text extraction starts with parsing a document, rather than reading the file as a plain string. A PDF stores text in positioned pieces, and the order of those pieces may differ from the order in which a person reads the page.
This tutorial covers two browser examples: PDF.js(opens in a new tab) for open source text extraction, and Nutrient Web SDK for extraction through a document viewer. You’ll get a string containing text from every page, with guidance for missing spaces, scanned documents, and complex layouts.
Requirements for extracting text from a PDF in JavaScript
You’ll need a modern browser, Node.js and npm for the setup commands, and a PDF with selectable text. Create a working directory and save your PDF there as document.pdf. Our demo document is a starting point.
Serve the directory over HTTP instead of opening an HTML file directly. From that directory, run:
npx serve -l 8080 .The examples use local library files so each worker matches its main library version. Run the setup commands in a second terminal while the server is running.
Extracting text from a PDF file using PDF.js
Install PDF.js in your working directory:
npm install pdfjs-dist@6.3.289Save the following as pdfjs.html. Open http://localhost:8080/pdfjs.html after installation:
<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <title>Extract PDF text with PDF.js</title> </head> <body> <pre id="output">Extracting text…</pre> <script type="module"> import * as pdfjsLib from './node_modules/pdfjs-dist/build/pdf.mjs';
pdfjsLib.GlobalWorkerOptions.workerSrc = './node_modules/pdfjs-dist/build/pdf.worker.mjs';
async function extractText(pdfUrl) { const loadingTask = pdfjsLib.getDocument({ url: pdfUrl, cMapUrl: './node_modules/pdfjs-dist/cmaps/', cMapPacked: true, standardFontDataUrl: './node_modules/pdfjs-dist/standard_fonts/', wasmUrl: './node_modules/pdfjs-dist/wasm/', });
try { const pdf = await loadingTask.promise; const pages = [];
for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) { const page = await pdf.getPage(pageNumber); const content = await page.getTextContent(); const text = content.items .filter((item) => 'str' in item) .map((item) => item.str + (item.hasEOL ? '\n' : ' ')) .join('') .trim(); pages.push(text); page.cleanup(); }
return pages.join('\n\n'); } finally { await loadingTask.destroy(); } }
const output = document.getElementById('output'); try { const text = await extractText('./document.pdf'); output.textContent = text.trim() ? text : 'No text layer found. This document may need OCR.'; console.log(text); } catch (error) { output.textContent = `Extraction failed: ${error.message}`; console.error(error); } </script> </body></html>The function loads pages sequentially rather than starting work on every page at once. It releases page resources after extraction and destroys the loading task in a finally block, including when an error occurs.
Understanding the PDF.js text output
getTextContent()(opens in a new tab) returns an object containing an items array. Text items have a str property, position information, and a hasEOL flag. The filter skips any non-text items.

The example inserts a line break when hasEOL is true, and a space otherwise. This spacing rule separates adjacent words, but it can also insert spaces inside a word split across several items. It doesn’t reconstruct tables or guarantee reading order in multicolumn documents.
For layout-sensitive extraction, inspect each item’s coordinates, font information, and surrounding items instead of relying on a single join() rule. The two newlines between pages keep the last word on one page from merging with the first word on the next.

PDF.js vs. Nutrient for text extraction
Choose based on the output and document features your application needs. Neither example performs OCR or reconstructs tables from the extracted text.
| Requirement | PDF.js | Nutrient Web SDK |
|---|---|---|
| Text API | getTextContent() returns positioned text items | textLinesForPageIndex() returns text line objects |
| Page numbering | Starts at one | Starts at zero |
| Layout information | Text item transforms and dimensions | Line contents and bounding boxes |
| Image-only pages | Need a separate OCR step | Need a separate OCR workflow; this standalone example doesn’t run OCR |
| Integration | Build extraction into your own application | Extract through an SDK instance with a document viewer |
| Licensing | Apache 2.0 | Commercial, with a trial |
PDF.js fits an application that needs an open source parser and control over text processing. Nutrient is useful when extraction is part of a viewer with features such as annotations, redaction, or form filling, depending on your license. Test both with your own files before choosing.
Adding Nutrient to your project
In the same working directory, install the SDK and copy its JavaScript file into an assets directory. Download the matching runtime archive there too:
npm install @nutrient-sdk/viewer@1.21.0mkdir -p assetscp node_modules/@nutrient-sdk/viewer/dist/nutrient-viewer.js assets/curl --fail --location \ https://cdn.cloud.nutrient.io/assets/pspdfkit-web-assets@1.21.0.zip \ --output nutrient-assets.zipunzip nutrient-assets.zip -d assetsThe extracted directory must be assets/nutrient-viewer-lib/. You can copy and extract the files manually if those shell commands aren’t available on your system. Keep the JavaScript package and runtime archive on the same version when following the self-hosting instructions.
Extracting text from a PDF using Nutrient
Save this example as nutrient.html and open http://localhost:8080/nutrient.html:
<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <title>Extract PDF text with Nutrient</title> </head> <body> <pre id="output">Extracting text…</pre> <div id="nutrient" style="height: 80vh"></div> <script src="./assets/nutrient-viewer.js"></script> <script type="module"> const output = document.getElementById('output'); const controller = new AbortController();
window.addEventListener('pagehide', (event) => { if (!event.persisted) { controller.abort(); NutrientViewer.unload('#nutrient'); } });
try { const response = await fetch('./document.pdf', { signal: controller.signal, }); if (!response.ok) { throw new Error(`PDF download failed: ${response.status}`); } const pdfData = await response.arrayBuffer(); const instance = await NutrientViewer.load({ container: '#nutrient', document: pdfData, baseUrl: new URL('./assets/', window.location.href).href, signal: controller.signal, // Add your production license key here when deploying. }); const pages = [];
for (let pageIndex = 0; pageIndex < instance.totalPageCount; pageIndex++) { const lines = await instance.textLinesForPageIndex(pageIndex); pages.push(lines.map((line) => line.contents).join('\n')); }
const text = pages.join('\n\n'); output.textContent = text.trim() ? text : 'No text layer found. This document may need OCR.'; console.log(text); } catch (error) { if (!controller.signal.aborted) { output.textContent = `Extraction failed: ${error.message}`; console.error(error); } } </script> </body></html>textLinesForPageIndex() returns an immutable list of text lines. Each line provides contents and a boundingBox, which is useful when connecting extracted text to a location in the viewer. The page index starts at zero, unlike PDF.js’s page number.
The example checks the HTTP response before passing the PDF bytes to the SDK. The viewer stays open after extraction so you can compare the text with the page. For deployment, supply your licenseKey and follow the Web SDK integration guide.

Troubleshooting PDF text extraction
An empty result can mean the page contains only images. If it already has an OCR text layer, these examples can read that layer; otherwise, add OCR first. Nutrient’s OCR workflow with Document Engine requires the appropriate licensed components and a server-backed setup.
If you get a worker version mismatch, use the main library and worker from the same PDF.js release. If a local file won’t load, check its URL and serve the example over HTTP. A PDF hosted on another origin also needs suitable cross-origin resource sharing (CORS) headers.
Scrambled reading order, missing characters, or unexpected spaces can come from the PDF’s content order or font mappings. Keep a test set with columns, tables, non-Latin text, and scanned pages. Then compare extracted output with what a reader sees. A non-empty string isn’t proof that every word was recovered correctly.
Conclusion
Use PDF.js’s text items when you want to control extraction and layout processing yourself. Use Nutrient’s text lines when extraction belongs in a document viewer with additional PDF capabilities. Both examples return text from all pages, but structured fields, tables, and image-only content need further processing.
To add extraction to your viewer, start with the Nutrient Web SDK integration guide. For a server workflow, see Document Engine’s page text endpoint. To compare extracted document versions, see PDF text comparison.
FAQ
With PDF.js, loop from one through pdf.numPages, load each page, and call getTextContent(). With Nutrient, loop from zero to one less than instance.totalPageCount and call textLinesForPageIndex(). Join the results with explicit page separators.
It can read an existing text layer, including one added by OCR. It doesn’t recognize text from page images itself. An image-only scan needs a separate OCR step before text extraction.
PDF text is positioned on the page, and its storage order may differ from its reading order. Joining text items can lose spaces, add unwanted spaces, or mix columns. Use position information and test the output with your document layouts.
The pdfjs-dist package also supports Node.js. Follow its Node.js examples(opens in a new tab) for the appropriate imports and runtime requirements; the browser HTML and worker setup above isn’t a Node.js entry point. You can also call Document Engine’s page text endpoint from a backend application.
You’d need to implement PDF parsing, stream decoding, and font handling yourself. Reading PDF bytes as plain text won’t reliably recover the document’s text. A PDF parser is the practical starting point.