mrkeyoor.com_
Sat 08 Aug 20:58 UTC
npmDataupdated 08 Aug 2026

pdfkit

PDFKit is an imperative PDF generator for Node.js and browser bundles. You create a document stream, place wrapped text, embedded fonts, JPEG or PNG images, vector paths, tables, links, forms, outlines, encryption settings, and accessibility structure, then finalize the stream into a PDF. It gives low-level drawing control and some layout helpers, but it is not an HTML/CSS renderer and does not paginate an existing web page for you.

Verdict

PDFKit is a capable choice when the PDF itself is the canvas and streaming, fonts, drawing, and document features matter. Choose an HTML renderer or declarative system when page layout is the problem, because PDFKit makes that work yours.

API stability4/5The chainable PDFDocument API, Node stream model, text, font, image, vector, page, annotation, and finalization calls have remained recognizable for many years. New tables, PDF/A, encryption, forms, and accessibility capabilities are additive, though the lack of an exports map and bundled TypeScript declarations leaves module and typing ergonomics behind newer packages.
Docs4/5The official programming guide has concrete sections for pages, text measurement, vector drawing, images, tables, annotations, forms, outlines, destinations, attachments, encryption, PDF/A, accessibility, and browser setup. Examples are practical and the README exposes the brfs trap. Some pages and navigation lag repository additions, and the site is still served over HTTP.
Maintenance5/5npm 0.19.1 was published on 2026-06-10. The repository had 10,692 stars, 344 open issues and PRs, and a push on 2026-08-08, with current tests covering tables and visual snapshots. The sizeable issue queue reflects a broad PDF feature surface and many renderer edge cases, but both code and releases are clearly active.
Ecosystem4/5PDFKit works in Node and browser bundles, streams into files and HTTP responses, uses fontkit for advanced font shaping, and documents blob-stream for browser output. It covers unusually many PDF-native features without Chromium. The trade is an imperative ecosystem with fewer layout abstractions and no official TypeScript declaration surface in 0.19.1.

Use it if

  • You generate invoices, reports, tickets, labels, or certificates from structured data and want exact drawing control
  • You need streaming output to a file or HTTP response rather than launching a browser process
  • You need embedded fonts, vector graphics, tables, annotations, AcroForms, outlines, encryption, or Tagged PDF in one library
  • You can own page flow and visual regression tests instead of expecting browser layout rules
Skip it if

Setup reality

npm install pdfkit is enough for Node's CommonJS API, and 0.19.1 also publishes an ES module build. The document is a readable stream: pipe it to a writable file, HTTP response, buffer collector, or browser Blob stream before adding content, and always call doc.end() so cross-reference tables and downstream finish events are written. Output is incremental by default, which keeps memory down but means earlier pages cannot be edited after they flush. Enable bufferPages when total-page numbering or backfilling is required, accepting that every buffered page stays in memory. Custom fonts must be available as files or buffers at runtime; PDF/A requires embedded fonts rather than the built-in AFM standard fonts. Text and tables can add pages, but application code still owns headers, footers, orphan rules, section continuity, and brand layout. Browser use adds more work: the README documents Browserify, webpack, or the standalone build, requires a separate sink such as blob-stream, and warns Browserify users to install brfs because built-in font data is otherwise missing. The npm package has no declared TypeScript types. Treat user content carefully because links, attachments, forms, metadata, and encryption options alter the document, and password protection is not a substitute for access control. Accessibility is also manual: Tagged PDF needs logical structure, meaningful reading order, alt text, language, and artifact marking; merely calling text and image does not make a compliant PDF. Finally, PDF rendering differs across Acrobat, browsers, macOS Preview, and print systems, so use fixture PDFs or rasterized visual comparisons and test real fonts, long text, page breaks, and large data sets before production.

Patterns

Stream a PDF to a filewrite-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();

Call end after adding all content; the writable stream finishes only after PDFKit writes the document trailer.

Send a generated PDF over HTTPstream-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 headers before piping and handle stream errors or client disconnects; generation failures after headers cannot become a normal JSON error response.

Flow text into columnswrap-multicolumn-text

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

PDFKit advances doc.x and doc.y as it lays out text; measure or reset position before placing unrelated content afterward.

Register and reuse embedded fontsregister-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');

Font files must exist in the deployed filesystem or be passed as buffers; embedding the actual fonts is required for PDF/A.

Fit an image inside a boxplace-image

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

PDFKit directly supports JPEG and PNG; convert SVG, WebP, and other formats or draw them through supported vector paths.

Draw a filled and stroked shapedraw-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 isolate fill, stroke, and transform state; forgetting them lets style changes bleed into later content.

Render a table with fixed and flexible columnsrender-table

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

Tables can paginate, but test long cells, spans, fonts, and header behavior against the exact 0.19.x output you ship.

Add content whenever a page is createdadd-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 initial page exists before this listener runs; draw its header separately or create the document with autoFirstPage: false.

Backfill total page numbersadd-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 retains every page until flush or end, which increases memory use for large reports.

Create linked textadd-clickable-link

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

Treat URLs as untrusted input and reset link or style options before subsequent text, especially when using continued segments.

Add a paragraph to the logical structuretag-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. ');
}));

Tagged output requires all meaningful content in a correct structure and decorative content marked as artifacts; one tagged paragraph is not PDF/UA compliance.

Generate a downloadable PDF in the browsercreate-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);
});

blob-stream is a separate dependency, and Browserify also needs brfs to include PDFKit's built-in font data.

Alternatives

PackageRegistryPick it when
pdf-libnpmYou need to create and modify existing PDFs in JavaScript with a modern object-oriented API
jspdfnpmYour priority is straightforward client-side PDF creation and a large plugin ecosystem
puppeteernpmYour document is naturally expressed as HTML and CSS and Chromium deployment is acceptable
@react-pdf/renderernpmYou prefer declarative React components and stylesheet-driven document layout