Getting Started with PDFKit

Installation

Installation uses the npm package manager. Just type the following command after installing npm.

npm install pdfkit

Creating a document

Creating a PDFKit document is quite simple. Require the named PDFDocument export and create an instance of the class.

const { PDFDocument } = require('pdfkit');
const doc = new PDFDocument();

Or, from an ES module:

import { PDFDocument } from 'pdfkit';
const doc = new PDFDocument();

In Node, both require('pdfkit') and import 'pdfkit' resolve to a Node build with real file system access, native zlib compression and Node streams; browsers and bundlers targeting browsers get the browser build described below. Both const PDFDocument = require('pdfkit') and import PDFDocument from 'pdfkit' remain supported for backward compatibility. New code should prefer the named PDFDocument export, which will make a future migration to an ESM-only package more straightforward.

PDFDocument instances are readable Node streams. They don't get saved anywhere automatically, but you can call the pipe method to send the output of the PDF document to another writable Node stream as it is being written. When you're done with your document, call the end method to finalize it. Here is an example showing how to pipe to a file or an HTTP response.

doc.pipe(fs.createWriteStream('/path/to/file.pdf')); // write to PDF
doc.pipe(res);                                       // HTTP response

// add stuff to PDF here using methods described below...

// finalize the PDF and end the stream
doc.end();

The write and output methods found in PDFKit before version 0.5 are now deprecated.

Using PDFKit in the browser

PDFKit provides an ES module build for browsers. Standard PDF font metrics are not included in the main browser module. Import and register the generated data for every standard font your application uses before selecting that font. The default document font is Helvetica, so it must be registered before constructing a document unless the document is created with { font: null }.

Experimental output helpers

PDFKit provides experimental toBlob and toBytes helpers from pdfkit/output. These functions may change before they are stabilized. Call the selected helper before ending the document so it receives the complete output.

Use toBlob when displaying, downloading or uploading the PDF in a browser:

import { PDFDocument, registerStdFonts } from 'pdfkit';
import Helvetica from 'pdfkit/standard-fonts/Helvetica';
import { toBlob } from 'pdfkit/output';

registerStdFonts(Helvetica);

const doc = new PDFDocument();
const output = toBlob(doc);

// add your content to the document here, as usual

doc.end();
const blob = await output;
const url = URL.createObjectURL(blob);
iframe.src = url;

// Revoke the URL when the iframe no longer needs the PDF.
// URL.revokeObjectURL(url);

Use toBytes instead when a binary API, worker or parser needs one contiguous Uint8Array:

import { toBytes } from 'pdfkit/output';

const output = toBytes(doc);
doc.end();
const bytes = await output;

The stable, dependency-free alternative is to collect the document's Uint8Array chunks using its events and construct the Blob directly:

const chunks = [];

doc.on('data', chunk => chunks.push(chunk));
doc.on('end', () => {
  const blob = new Blob(chunks, { type: 'application/pdf' });
  const url = URL.createObjectURL(blob);
  iframe.src = url;
});

// add your content to the document here, as usual
doc.end();

The browser build does not depend on Node's stream module. A document is still a readable stream, but only the parts a PDF document needs are implemented: on, once, off, emit, pipe and async iteration. read, setEncoding, destroy, stream.pipeline and the readable, error and close events are available in Node only.

The browser build cannot read from the file system. To keep using paths with registerFont, image or file, register a Uint8Array under the exact path first:

import { PDFDocument, registerFile } from 'pdfkit';

const response = await fetch('/fonts/Roboto-Regular.ttf');
const fontData = new Uint8Array(await response.arrayBuffer());

registerFile('fonts/Roboto-Regular.ttf', fontData);

const doc = new PDFDocument();
doc.registerFont('Roboto', 'fonts/Roboto-Regular.ttf');
doc.font('Roboto').text('This font was loaded from memory.');

// Unregister the path when it is no longer needed.
registerFile('fonts/Roboto-Regular.ttf', undefined);

Registration is global to the loaded PDFKit module. Registering the same path again replaces its bytes. Passing undefined unregisters the path and does nothing if it was not registered. Unregistered paths throw in browsers.

The same registry is available from the Node entry points. Registered data takes precedence over a file at the same path; unregistering restores normal file system lookup.

const { PDFDocument, registerFile } = require('pdfkit');

registerFile('files/example.txt', new Uint8Array([1, 2, 3]), {
  birthtime: new Date('2020-01-02T03:04:05Z'),
  ctime: new Date('2021-02-03T04:05:06Z'),
});
registerFile('files/example.txt', undefined);

registerFile accepts only a Uint8Array or undefined. A Uint8Array or ArrayBuffer can still be passed directly to registerFont, image and file, and a data URL can be passed directly to image and file, but these other representations cannot be stored with registerFile. The optional birthtime and ctime values must be valid Date objects. Any omitted timestamp defaults to the time of registration.

You can see an interactive in-browser demo of PDFKit here.

Document options

When creating a PDFDocument, you can pass various options to control the document behavior.

Page Layout

The pageLayout option specifies how pages should be displayed in a PDF viewer:

Value Description
singlePage Display one page at a time
oneColumn Display pages in one column
twoColumnLeft Display pages in two columns, odd pages on left
twoColumnRight Display pages in two columns, odd pages on right
twoPageLeft Display two pages at a time, odd pages on left
twoPageRight Display two pages at a time, odd pages on right
const doc = new PDFDocument({ pageLayout: 'twoColumnLeft' });

Adding pages

The first page of a PDFKit document is added for you automatically when you create the document unless you provide autoFirstPage: false. Subsequent pages must be added by you. Luckily, it is quite simple!

doc.addPage()

To add some content every time a page is created, either by calling addPage() or automatically, you can use the pageAdded event.

doc.on('pageAdded', () => doc.text("Page Title"));

You can also set some options for the page, such as its size and orientation.

The layout property can be either portrait (the default) or landscape. The size property can be either an array specifying [width, height] in PDF points (72 per inch), or a string specifying a predefined size. A list of the predefined paper sizes can be seen here. The default is letter.

Passing a page options object to the PDFDocument constructor will set the default paper size and layout for every page in the document, which is then overridden by individual options passed to the addPage method.

You can set the page margins in two ways. The first is by setting the margin / margins property to a single value, which applies that to all edges. The other way is to provide an object with top, right, bottom, and left values. By default, using a number this will be in points (the default PDF unit), however you can provide any of the following units inside a string and this will be converted for you: em, in, px, cm, mm, pc, ex, ch, rem, vw, vmin, vmax, %, pt. For those which are based on text sizes this will take the size of the font for the page (excluding rem which is always the document root font size) The default is a 1 inch (72 point) margin on all sides.

For example:

// Add a 50 point margin on all sides
doc.addPage({ margin: 50 });

// Add a 2 inch margin on all sides
doc.addPage({ margin: '2in' });

// Add a 2em(28pt) margin using the font size
doc.addPage({ fontSize: 14, margin: '2em' });

// Add different margins on each side
doc.addPage({
  margins: {
    top: 50,
    bottom: 50,
    left: 72,
    right: 72
  }
});

Switching to previous pages

PDFKit normally flushes pages to the output file immediately when a new page is created, making it impossible to jump back and add content to previous pages. This is normally not an issue, but in some circumstances it can be useful to add content to pages after the whole document, or a part of the document, has been created already. Examples include adding page numbers, or filling in other parts of information you don't have until the rest of the document has been created.

PDFKit has a bufferPages option in versions v0.7.0 and later that allows you to control when pages are flushed to the output file yourself rather than letting PDFKit handle that for you. To use it, just pass bufferPages: true as an option to the PDFDocument constructor. Then, you can call doc.switchToPage(pageNumber) to switch to a previous page (page numbers start at 0).

When you're ready to flush the buffered pages to the output file, call flushPages. This method is automatically called by doc.end(), so if you just want to buffer all pages in the document, you never need to call it. Finally, there is a bufferedPageRange method, which returns the range of pages that are currently buffered. Here is a small example that shows how you might add page numbers to a document.

// create a document, and enable bufferPages mode
let i;
let end;
const doc = new PDFDocument({
  bufferPages: true});

// add some content...
doc.addPage();
// ...
doc.addPage();

// see the range of buffered pages
const range = doc.bufferedPageRange(); // => { start: 0, count: 2 }

for (i = range.start, end = range.start + range.count, range.start <= end; i < end; i++) {
  doc.switchToPage(i);
  doc.text(`Page ${i + 1} of ${range.count}`);
}

// manually flush pages that have been buffered
doc.flushPages();

// or, if you are at the end of the document anyway,
// doc.end() will call it for you automatically.
doc.end();

Setting default font

The default font is 'Helvetica'. It can be configured by passing font option

// use Courier font by default
const doc = new PDFDocument({font: 'Courier'});

Setting document metadata

PDF documents can have various metadata associated with them, such as the title, or author of the document. You can add that information by adding it to the doc.info object, or by passing an info object into the document at creation time.

Here is a list of all of the properties you can add to the document metadata. According to the PDF spec, each property must have its first letter capitalized.

Encryption and Access Privileges

PDF specification allow you to encrypt the PDF file and require a password when opening the file, and/or set permissions of what users can do with the PDF file. PDFKit implements standard security handler in PDF version 1.3 (40-bit RC4), version 1.4 (128-bit RC4), PDF version 1.7 (128-bit AES), and PDF version 1.7 ExtensionLevel 3 (256-bit AES).

To enable encryption, provide a user password when creating the PDFDocument in options object. The PDF file will be encrypted when a user password is provided, and users will be prompted to enter the password to decrypt the file when opening it.

To set access privileges for the PDF file, you need to provide an owner password and permission settings in the option object when creating PDFDocument. By default, all operations are disallowed. You need to explicitly allow certain operations.

Following settings are allowed in permissions object:

You can specify either user password, owner password or both passwords. Behavior differs according to passwords you provides:

Note that PDF file itself cannot enforce access privileges. When file is decrypted, PDF viewer applications have full access to the file content, and it is up to viewer applications to respect permission settings.

To choose encryption method, you need to specify PDF version. PDFKit will choose best encryption method available in the PDF version you specified.

Available options includes:

When using PDF version 1.7 ExtensionLevel 3, password is truncated to 127 bytes of its UTF-8 representation. In older versions, password is truncated to 32 bytes, and only Latin-1 characters are allowed.

PDF/A

PDF/A is a standard (ISO 19005-1:2005) which defines rules for electornic documents intended for long-term archiving. The restrictions on PDF/A documents are:

Currently, PDFKit aims to support PDF/A-1b, PDF/A-2b, PDF/A-3b and PDF/A-1a, PDF/A-2a, PDF/A-3a standards, also known as level B conformance and level A conformance, respectively.

In order to create PDF/A documents, set subset to either PDF/A-1 or PDF/A-1b for level B (basic) conformance, or PDF/A-1a for level A (accessible) conformance when creating the PDFDocument in options object.

Similary, use PDF/A-2 or PDF/A-2b for PDF/A-2 level B conformance and PDF/A-2a for PDF/A-2 level A conformance. PDF/A-3 or PDF/A-3b can be used for PDF/A-3 level B conformance and PDF/A-3a for PDF/A-3 level A conformance.

Futhermore, you will need to specify the other options relevant to the PDF/A subset you wish to use, for PDF/A-1 being:

For PDF/A-2 and PDF/A-3, the pdfVersion needs to be set to at least 1.7 and tagged needs to be true for level A conformance.

In order to verify the generated document for PDF/A and its subsets conformance, veraPDF is an excellent open source validator.

Please note that PDF/A requires fonts to be embedded, as such the standard fonts PDFKit comes with cannot be used because they are in AFM format, which only provides neccessary metrics, without the font data. You should use registerFont() and use embeddable fonts such as ttf.

Adding content

Once you've created a PDFDocument instance, you can add content to the document. Check out the other sections described in this document to learn about each type of content you can add.

That's the basics! Now let's move on to PDFKit's powerful vector graphics abilities.