mrkeyoor.com_
Wed 23 Sept 00:32 UTC
npmDataupdated 22 Sept 2026

pdfkit review

PDFKit 0.20.1 builds PDFs through drawing and document commands in Node or a browser. A `PDFDocument` is a readable stream that accepts positioned text, embedded fonts, JPEG and PNG images, vector paths, tables, forms, links, outlines, encryption options, and tagged structure. The current release steers new code toward the named `PDFDocument` export and adds experimental `pdfkit/output` helpers plus browser-side file registration. It remains a PDF canvas API, so it does not interpret an HTML page or CSS layout.

Verdict

PDFKit 0.19.1 used 24 MB and installed in 2.2 seconds with 0 audit findings in our sandbox; npm has since published 0.20.1 with new browser output plumbing. Use it when drawing and streaming a PDF is the job, and use Chromium or a declarative renderer when automatic page layout is the job.

We installed it

Lab card: what happened when we installed pdfkitScreenshot of pdfkit documentation
Install✓ · 2.2s21 packages on disk · 24 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does pdfkit install cleanly?

Yes. In a fresh container with an empty cache, npm install pdfkit finished in 2 seconds, leaving 21 packages and 24 MB on disk. npm audit reported no known vulnerabilities.

Can pdfkit run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does pdfkit work with both ESM and CommonJS?

Yes. Both import 'pdfkit' and require('pdfkit') worked in Node 22 in our run. The package is published as CommonJS.

Does pdfkit include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

pdfkit or pdf-lib: which should you use?

pdf-lib: Choose it when existing PDFs must be loaded and modified as well as created. PDFKit 0.19.1 used 24 MB and installed in 2.2 seconds with 0 audit findings in our sandbox; npm has since published 0.20.1 with new browser output plumbing.

When should you not use pdfkit?

The document already exists as HTML and CSS. PDFKit has no browser layout engine, so Chromium printing usually preserves that source with less code.

API stability4/5The document stream, chainable drawing calls, text, fonts, images, pages, annotations, and `end()` have stayed recognizable for years. Version 0.20.1 keeps default import compatibility while asking new code to use the named `PDFDocument` export. Its `pdfkit/output` helpers are explicitly experimental, so code depending on them accepts a less settled contract.
Docs4/5The programming guide covers text measurement, vector operations, images, tables, forms, outlines, destinations, attachments, encryption, PDF/A, accessibility, and browser packaging with runnable examples. The 0.20.1 README now explains file registration and output helpers. The main site still uses HTTP, and some newer entry-point details are easier to find in the repository README than its navigation.
Maintenance5/5npm currently serves 0.20.1, and GitHub records a push on August 25, 2026. The unarchived project has 10,697 stars and reports 330 open issues and pull requests. Recent work on exports, browser files, output collection, tables, and visual tests shows active maintenance, while the large issue queue reflects the number of PDF features and viewer-specific cases.
Ecosystem4/5npm counted 6,188,001 downloads in the week ending August 24, 2026. PDFKit can stream through Node, feed HTTP responses, run through browser-specific builds, shape fonts through fontkit, and cover many PDF-native features without Chromium. Its surrounding tools offer fewer high-level layout conventions, and current package metadata still provides no TypeScript declarations.

Use it if

  • Structured invoice, report, label, ticket, or certificate data must become a precisely drawn PDF.
  • A Node endpoint should stream bytes directly to a file or HTTP response without starting Chromium.
  • The document needs PDF-native features such as embedded fonts, vector paths, AcroForms, outlines, encryption, or tagged content.
  • The team can write page-flow rules and compare rendered fixtures across the viewers it supports.
Skip it if

Setup reality

We installed PDFKit 0.19.1 in a clean Node 22 Bookworm sandbox. npm completed in 2.2 seconds, left 21 packages using 24 MB, and reported 0 vulnerabilities at all severities. That measured package had 6 direct dependencies, no peers, and 8324 KB unpacked. It shipped no TypeScript declarations. npm now serves 0.20.1, so we did not assign the 0.19.1 size or audit result to the newer patch.

The measured package was CommonJS without an exports map; both require() and ESM import worked. Current documentation prefers the named PDFDocument export for a future ESM-only transition. A browser build of 0.19.1 failed under our generic esbuild check. The 0.20.1 README instead documents browser-specific entry points, explicit standard-font registration, and experimental toBlob and toBytes helpers.

Pipe or attach an output collector before adding content, then call doc.end() once. Streaming saves memory, but flushed pages cannot be edited. Turn on bufferPages only when later code must add totals or backfill earlier pages, since every retained page costs memory.

Font files or buffers must exist where the document is generated, and PDF/A requires embedded fonts. Browser code has no filesystem, so 0.20.1 adds module-wide registerFile() data for path-based calls. Headers, footers, orphan handling, reading order, and long-table behavior remain your code. Test real fonts and page breaks in Acrobat, browsers, Preview, and any print path you promise to support.

Patterns

Write a PDF file stream write-pdf-file

const PDFDocument = require('pdfkit');
const fs = require('node:fs');

const doc = new PDFDocument({ size: 'A4', margin: 50 });
doc.pipe(fs.createWriteStream('invoice.pdf'));
doc.fontSize(20).text('Invoice');
doc.end();

`doc.end()` writes the trailer and allows the destination to finish. Omitting it leaves consumers waiting on an incomplete document.

Pipe a PDF into an HTTP response stream-http-response

res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'attachment; filename=report.pdf');

const doc = new PDFDocument();
doc.pipe(res);
doc.text('Quarterly report');
doc.end();

Set response headers first. Once PDF bytes are sent, a generation error cannot be replaced with an ordinary JSON error body.

Flow text through two columns wrap-multicolumn-text

doc.font('Times-Roman').fontSize(11).text(body, {
  width: 495,
  columns: 2,
  columnGap: 24,
  align: 'justify',
  paragraphGap: 8,
});

Text layout advances the document cursor. Record or reset `doc.x` and `doc.y` before placing unrelated elements.

Embed deployment fonts register-custom-font

doc.registerFont('Body', 'assets/Inter-Regular.ttf');
doc.registerFont('Body Bold', 'assets/Inter-Bold.ttf');

doc.font('Body').fontSize(11).text('Normal text');
doc.font('Body Bold').text('Important text');

The exact font files must be present as paths or buffers. PDF/A output cannot rely on the built-in unembedded AFM fonts.

Fit a supported raster image place-image

doc.image('assets/logo.png', 50, 60, {
  fit: [160, 80],
  align: 'center',
  valign: 'center',
});

JPEG and PNG work directly. Convert WebP or other raster formats before passing them to this API.

Isolate vector drawing state draw-vector-card

doc.save()
  .roundedRect(50, 160, 495, 90, 8)
  .fillAndStroke('#f8fafc', '#94a3b8')
  .fillColor('#0f172a')
  .fontSize(14)
  .text('Account summary', 70, 185)
  .restore();

`save()` and `restore()` contain transforms, fills, and strokes so later page elements do not inherit them.

Lay out a paginated table render-table

doc.table({
  columnStyles: [120, '*', 80],
  rowStyles: (row) => row === 0 ? { backgroundColor: '#e2e8f0' } : {},
  data: [
    ['SKU', 'Description', 'Amount'],
    ['A-10', 'Annual plan', '$120.00'],
  ],
});

Exercise long cells, spans, headers, and custom fonts against the same PDFKit version used in production.

Draw a header on added pages add-page-header

const doc = new PDFDocument({ margin: 60 });

doc.on('pageAdded', () => {
  doc.fontSize(9).fillColor('gray').text('ACME REPORT', 60, 30, { align: 'right' });
  doc.fillColor('black');
});

The first page exists before this listener is attached. Draw its header separately or set `autoFirstPage: false`.

Add totals after pagination add-page-numbers

const doc = new PDFDocument({ bufferPages: true });
// Add all document content here.
const range = doc.bufferedPageRange();
for (let i = range.start; i < range.start + range.count; i += 1) {
  doc.switchToPage(i);
  doc.text(`Page ${i + 1} of ${range.count}`, 50, 780, { align: 'center' });
}
doc.end();

`bufferPages` keeps pages editable until flush or end and raises memory use as reports grow.

Create an external link add-clickable-link

doc.fillColor('blue').text('View order online', {
  link: 'https://example.com/orders/42',
  underline: true,
});
doc.fillColor('black');

Validate untrusted destinations and reset link and color options before continuing ordinary text.

Place text in the structure tree tag-accessible-paragraph

const doc = new PDFDocument({ lang: 'en-US', tagged: true });

doc.addStructure(doc.struct('P', () => {
  doc.text('This paragraph participates in the reading order. ');
}));

One tagged paragraph does not meet PDF/UA. Every meaningful element needs correct order, semantics, and artifact handling.

Collect browser output as a Blob create-browser-blob

const PDFDocument = require('pdfkit');
const blobStream = require('blob-stream');

const doc = new PDFDocument();
const stream = doc.pipe(blobStream());
doc.text('Browser-generated PDF');
doc.end();

stream.on('finish', () => {
  const url = stream.toBlobURL('application/pdf');
  window.open(url);
});

Browser output needs an explicit collector. In 0.20.1, `pdfkit/output` provides experimental helpers as another option.

Alternatives

PackageRegistryPick it when
pdf-libnpmChoose it when existing PDFs must be loaded and modified as well as created.
jspdfnpmChoose it for client-side generation and its established browser plugin collection.
puppeteernpmChoose it when HTML and CSS are the document source and Chromium deployment is acceptable.
@react-pdf/renderernpmChoose it for React components and stylesheet-driven PDF layout.

More data guides

numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.