Skip to content
l2yshoPublic

About

A fast, efficient, and minimal PDF parser for Node.js. Zero bloat. One dependency. Production-ready.

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Latest commit

 

History

686 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

afpp

Version codecov Mutation score Node npm Downloads Repo Size Last Commit

afpp — A modern, dependency-light PDF parser for Node.js.

Built for performance, reliability, and developer sanity.


Overview

afpp (Another PDF Parser, Properly) is a Node.js library for extracting text and images from PDF files without manual native build steps, event-loop blocking, or fragile runtime assumptions.

The project was created to address recurring problems encountered with existing PDF tooling in the Node.js ecosystem:

  • Excessive bundle sizes and transitive dependencies
  • Native build steps (canvas, ImageMagick, Ghostscript)
  • Browser-specific assumptions (window, DOM, canvas)
  • Poor TypeScript support
  • Unreliable handling of encrypted PDFs
  • Performance and memory inefficiencies

afpp focuses on predictable behavior, explicit APIs, and production-ready defaults.


Key Features

  • No manual build step required for text extraction; image rendering requires the optional peer dep @napi-rs/canvas (prebuilt native binaries, no compile step)
  • Fully asynchronous, non-blocking architecture
  • First-class TypeScript support
  • Supports local files, buffers, and remote URLs
  • Handles encrypted PDFs
  • Configurable concurrency and rendering scale
  • Minimal and auditable dependency graph

Requirements

  • Node.js >= 22.14.0

v3 breaking change: afpp is now ESM-only. Replace any require('afpp') calls with import ... from 'afpp'.


Installation

Install using your preferred package manager:

npm install afpp
# or
yarn add afpp
# or
pnpm add afpp

Quick Start

All parsing functions accept the same input types:

  • string (file path)
  • Buffer
  • Uint8Array
  • URL

A URL input is fetched by the library (http:, https: and file: are supported). Do not pass URLs that come from untrusted users; see Processing untrusted PDFs.

Extract Text from a PDF

import { pdf2string } from 'afpp';

const pages = await pdf2string('./document.pdf');
console.log(pages); // ['Page 1 text', 'Page 2 text', ...]

Render PDF Pages as Images

import { pdf2image } from 'afpp';

const url = new URL('https://pdfobject.com/pdf/sample.pdf');
const images = await pdf2image(url);

console.log(images); // [Buffer, Buffer, ...]

Streaming API (Large PDFs)

For large PDFs, use streaming functions to process pages incrementally without loading all results into memory:

import { writeFile } from 'fs/promises';

import { streamPdf2image, streamPdf2string } from 'afpp';

// Stream images - process each page as it's rendered
for await (const { pageNumber, pageCount, data } of streamPdf2image(
  './large.pdf',
)) {
  await writeFile(`page-${pageNumber}.png`, data);
  console.log(`Processed ${pageNumber}/${pageCount}`);
}

// Stream text - process each page as it's extracted
for await (const { pageNumber, data } of streamPdf2string('./large.pdf')) {
  console.log(`Page ${pageNumber}: ${data.substring(0, 100)}...`);
}

Benefits:

  • Lower peak memory usage
  • Faster time-to-first-result
  • Built-in progress tracking via pageNumber and pageCount

Extract PDF Metadata

import { getPdfMetadata } from 'afpp';

const metadata = await getPdfMetadata('./document.pdf');
console.log(metadata.pageCount); // e.g. 9
console.log(metadata.isEncrypted); // false
console.log(metadata.title); // 'My Document' or undefined
console.log(metadata.creationDate); // Date object or undefined

// Encrypted PDF
const meta = await getPdfMetadata('./secure.pdf', { password: 'secret' });
console.log(meta.isEncrypted); // true

Low-Level Parsing API

For advanced use cases, parsePdf exposes page-level control and transformation.

import { parsePdf } from 'afpp';

const response = await fetch('https://pdfobject.com/pdf/sample.pdf');
const buffer = Buffer.from(await response.arrayBuffer());

const result = await parsePdf(buffer, {}, (pageContent) => pageContent);
console.log(result);

Configuration

All public APIs accept a shared options object.

const result = await parsePdf(buffer, {
  concurrency: 5,
  imageEncoding: 'jpeg',
  password: 'STRONG_PASS',
  scale: 4,
});

AfppParseOptions

Option Type Default Description
concurrency number | 'auto' 1 Number of pages processed in parallel. Use 'auto' for CPU-based scaling.
imageEncoding 'png' | 'jpeg' | 'webp' | 'avif' 'png' Output format for rendered images
maxCanvasPixels number 2 ** 27 Largest page (width × height in pixels, after scale) that is rendered; larger pages reject. Embedded images above it are skipped.
password string — Password for encrypted PDFs
scale number 1.0 Rendering scale. Valid range: 0.1–10. (1.0 = 72 DPI, 2.0 = 144 DPI, 3.0 = 216 DPI)

PdfMetadata

Returned by getPdfMetadata. All fields except pageCount and isEncrypted are optional — absent metadata fields are undefined, never empty strings.

Field Type Description
pageCount number Total number of pages
isEncrypted boolean Whether the document required a password to open
title string? Document title
author string? Document author
subject string? Document subject
creator string? Application that created the document
producer string? PDF producer application
creationDate Date? Document creation date
modificationDate Date? Document last modification date

Processing untrusted PDFs

PDFs are a complex format, and a crafted file can be built to use as much CPU and memory as possible. When you parse files uploaded by users or fetched from third parties:

  • Never pass untrusted URLs. A URL input is fetched from your server, so a user-supplied URL can reach internal services or read local files through file:. Download the file yourself with your own allow-list, size limit and timeout, then pass the Buffer.
  • Limit the input size before parsing, for example by rejecting uploads above a few tens of megabytes.
  • Keep maxCanvasPixels as low as your use case allows. It bounds the memory and CPU of rendering one page. The default fits an A3 page at scale: 10; lower it if you only render at small scales.
  • Keep concurrency low. Peak memory grows with each page rendered in parallel.
  • Run parsing in isolation for hostile input: a worker thread with resourceLimits, or a child process with --max-old-space-size, that you terminate after a timeout. afpp has no built-in timeout, and native canvas memory is not covered by the V8 heap limit, so a container memory limit is the strongest bound.
  • Prefer text extraction (pdf2string, streamPdf2string) when you don't need images. It never allocates a canvas.

Design Principles

  • Node-first: No browser globals or DOM assumptions
  • Explicit over implicit: No magic configuration
  • Fail fast: Clear errors instead of silent corruption
  • Production-oriented: Optimized for long-running processes

Contributing

See CONTRIBUTING.md for development setup and pull request guidelines.


License

MIT © Richard Solár

About

A fast, efficient, and minimal PDF parser for Node.js. Zero bloat. One dependency. Production-ready.

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages