
Product
Socket for Jira Is Now Available
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.
Utilities for PDF extraction and rendering across all JavaScript runtimes – Node.js, Deno, Bun, the browser, and serverless environments like Cloudflare Workers. Especially useful for AI applications that need to summarize or analyze PDF documents.
Ships with a serverless build of Mozilla's PDF.js, optimized for edge environments. If you're coming from pdf-parse, unpdf is a modern, actively maintained alternative with broader runtime support.
unpdf/pdfjs)# pnpm
pnpm add unpdf
# npm
npm install unpdf
import { extractText, getDocumentProxy } from 'unpdf'
// Fetch a PDF from the web or load it from the file system
const buffer = await fetch('https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf')
.then(res => res.arrayBuffer())
const pdf = await getDocumentProxy(new Uint8Array(buffer))
const { totalPages, text } = await extractText(pdf, { mergePages: true })
console.log(`Total pages: ${totalPages}`)
console.log(text)
Usually you don't need to worry about the PDF.js build. unpdf ships with a serverless build of the latest PDF.js version. However, if you want to use the official PDF.js version or the legacy build, you can define a custom PDF.js module.
[!WARNING] PDF.js v5.x uses
Promise.withResolvers, which may not be supported in all environments, such as Node < 22. Consider using the bundled serverless build, which includes a polyfill, or use an older version of PDF.js.
For example, if you want to use the official PDF.js build:
import { definePDFJSModule, extractText, getDocumentProxy } from 'unpdf'
// Define the PDF.js build before using any other unpdf method
await definePDFJSModule(() => import('pdfjs-dist'))
// Now, you can use all unpdf methods with the official PDF.js build
const pdf = await getDocumentProxy(/* … */)
const { text } = await extractText(pdf)
unpdf provides helpful methods to work with PDF files, such as extractText and extractImages, which should cover most use cases. However, if you need more control over the PDF.js API, you can use the getResolvedPDFJS method to get the resolved PDF.js module.
Access the PDF.js API directly by calling getResolvedPDFJS:
import { getResolvedPDFJS } from 'unpdf'
const { version } = await getResolvedPDFJS()
[!NOTE] If no other PDF.js build was defined, the serverless build will always be used.
For example, you can use the getDocument method to load a PDF file and then use the getMetadata method to get the metadata of the PDF file:
import { readFile } from 'node:fs/promises'
import { getResolvedPDFJS } from 'unpdf'
const { getDocument } = await getResolvedPDFJS()
const data = await readFile('./dummy.pdf')
const document = await getDocument(new Uint8Array(data)).promise
console.log(await document.getMetadata())
[!NOTE] The serverless PDF.js bundle is built from PDF.js v5.6.205.
Heart and soul of this package is the pdfjs.rollup.config.ts file. It uses Rollup to bundle PDF.js into a single file for serverless environments. The key techniques:
FinalizationRegistry (unavailable in Cloudflare Workers).definePDFJSModuleAllows to define a custom PDF.js build. This method should be called before using any other method. If no custom build is defined, the serverless build will be used.
Type Declaration
function definePDFJSModule(pdfjs: () => Promise<PDFJS>): Promise<void>
getResolvedPDFJSReturns the resolved PDF.js module. If no other PDF.js build was defined, the serverless build will be used. This method is useful if you want to use the PDF.js API directly.
Type Declaration
function getResolvedPDFJS(): Promise<PDFJS>
getMetaExtracts metadata from a PDF. If parseDates is set to true, the date properties will be parsed into Date objects.
Type Declaration
function getMeta(
data: DocumentInitParameters['data'] | PDFDocumentProxy,
options?: {
parseDates?: boolean
},
): Promise<{
info: Record<string, any>
metadata: Record<string, any>
}>
extractTextExtracts all text from a PDF. If mergePages is set to true, the text of all pages will be merged into a single string. Otherwise, an array of strings for each page will be returned.
Type Declaration
function extractText(
data: DocumentInitParameters['data'] | PDFDocumentProxy,
options?: {
mergePages?: false
}
): Promise<{
totalPages: number
text: string[]
}>
function extractText(
data: DocumentInitParameters['data'] | PDFDocumentProxy,
options: {
mergePages: true
}
): Promise<{
totalPages: number
text: string
}>
extractLinksExtracts all links from a PDF document, including hyperlinks and external URLs.
Type Declaration
function extractLinks(
data: DocumentInitParameters['data'] | PDFDocumentProxy,
): Promise<{
totalPages: number
links: string[]
}>
Example
import { readFile } from 'node:fs/promises'
import { extractLinks, getDocumentProxy } from 'unpdf'
// Load a PDF file
const buffer = await readFile('./document.pdf')
const pdf = await getDocumentProxy(new Uint8Array(buffer))
// Extract all links from the PDF
const { totalPages, links } = await extractLinks(pdf)
console.log(`Total pages: ${totalPages}`)
console.log(`Found ${links.length} links:`)
for (const link of links) console.log(link)
extractImagesExtracts images from a specific page of a PDF document, including necessary metadata such as width, height, and calculated color channels. Works with both the serverless and official PDF.js build.
Type Declaration
interface ExtractedImageObject {
data: Uint8ClampedArray
width: number
height: number
channels: 1 | 3 | 4
key: string
}
function extractImages(
data: DocumentInitParameters['data'] | PDFDocumentProxy,
pageNumber: number,
): Promise<ExtractedImageObject[]>
Example
[!NOTE] The following example uses the sharp library to process and save the extracted images. You will need to install it with your preferred package manager.
import { readFile, writeFile } from 'node:fs/promises'
import sharp from 'sharp'
import { extractImages, getDocumentProxy } from 'unpdf'
async function extractPdfImages() {
const buffer = await readFile('./document.pdf')
const pdf = await getDocumentProxy(new Uint8Array(buffer))
// Extract images from page 1
const imagesData = await extractImages(pdf, 1)
console.log(`Found ${imagesData.length} images on page 1`)
// Process each image with sharp (optional)
let totalImagesProcessed = 0
for (const imgData of imagesData) {
const imageIndex = ++totalImagesProcessed
await sharp(imgData.data, {
raw: {
width: imgData.width,
height: imgData.height,
channels: imgData.channels
}
})
.png()
.toFile(`image-${imageIndex}.png`)
console.log(`Saved image ${imageIndex} (${imgData.width}x${imgData.height}, ${imgData.channels} channels)`)
}
}
extractPdfImages().catch(console.error)
renderPageAsImageTo render a PDF page as an image, you can use the renderPageAsImage method. This method will return an ArrayBuffer of the rendered image. It can also return a data URL (string) if toDataURL option is set to true.
[!NOTE] This method will only work in Node.js and browser environments.
In order to use this method, make sure to meet the following requirements:
@napi-rs/canvas package if you are using Node.js. This package is required to render the PDF page as an image.[!TIP] In Node.js,
getDocumentProxyautomatically setsdisableFontFace: trueand resolvesstandardFontDataUrlfrom your localpdfjs-distpackage for correct font rendering. To customize this behavior, pass your own options:const pdf = await getDocumentProxy(buffer, { disableFontFace: false, standardFontDataUrl: 'https://unpkg.com/pdfjs-dist@latest/standard_fonts/', })
Type Declaration
function renderPageAsImage(
data: DocumentInitParameters['data'] | PDFDocumentProxy,
pageNumber: number,
options?: {
canvasImport?: () => Promise<typeof import('@napi-rs/canvas')>
/** @default 1.0 */
scale?: number
width?: number
height?: number
toDataURL?: false
},
): Promise<ArrayBuffer>
function renderPageAsImage(
data: DocumentInitParameters['data'] | PDFDocumentProxy,
pageNumber: number,
options: {
canvasImport?: () => Promise<typeof import('@napi-rs/canvas')>
/** @default 1.0 */
scale?: number
width?: number
height?: number
toDataURL: true
},
): Promise<string>
Examples
import { definePDFJSModule, renderPageAsImage } from 'unpdf'
// Use the official PDF.js build
await definePDFJSModule(() => import('pdfjs-dist'))
const pdf = await readFile('./dummy.pdf')
const buffer = new Uint8Array(pdf)
const pageNumber = 1
const result = await renderPageAsImage(buffer, pageNumber, {
canvasImport: () => import('@napi-rs/canvas'),
scale: 2,
})
await writeFile('dummy-page-1.png', new Uint8Array(result))
import { definePDFJSModule, renderPageAsImage } from 'unpdf'
await definePDFJSModule(() => import('pdfjs-dist'))
const pdf = await readFile('./dummy.pdf')
const buffer = new Uint8Array(pdf)
const pageNumber = 1
const result = await renderPageAsImage(buffer, pageNumber, {
canvasImport: () => import('@napi-rs/canvas'),
scale: 2,
toDataURL: true,
})
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dummy Page</title>
</head>
<body>
<img alt="Example Page" src="${result}">
</body>
</html>`
await writeFile('dummy-page-1.html', html)
MIT License © 2023-PRESENT Johann Schopplich
FAQs
PDF extraction and rendering across all JavaScript runtimes
The npm package unpdf receives a total of 308,155 weekly downloads. As such, unpdf popularity was classified as popular.
We found that unpdf demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.

Company News
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.

Security News
NIST will stop enriching most CVEs under a new risk-based model, narrowing the NVD's scope as vulnerability submissions continue to surge.