Learn more at pdf-lib.js.org
Table of Contents
- Features
- Motivation
- Usage Examples
- Deno Usage
- Complete Examples
- Installation
- Documentation
- Encryption Handling
- Migrating to v1.0.0
- Contributing
- Tutorials and Cool Stuff
- Prior Art
- License
Features
- Create new PDFs
- Modify existing PDFs
- Add Pages
- Insert Pages
- Remove Pages
- Copy pages between PDFs
- Draw Text
- Draw Images
- Draw PDF Pages
- Draw Vector Graphics
- Draw SVG Paths
- Measure width and height of text
- Embed Fonts (supports UTF-8 and UTF-16 character sets)
- Set document metadata
- Read document metadata
- Add attachments
Motivation
pdf-lib
was created to address the JavaScript ecosystem's lack of robust support for PDF manipulation (especially for PDF modification).
Two of pdf-lib
's distinguishing features are:
- Supporting modification (editing) of existing documents.
- Working in all JavaScript environments - not just in Node or the Browser.
There are other good open source JavaScript PDF libraries available. However, most of them can only create documents, they cannot modify existing ones. And many of them only work in particular environments.
Usage Examples
Create Document
This example produces this PDF.
// Create a new PDFDocumentconst pdfDoc = await PDFDocument // Embed the Times Roman fontconst timesRomanFont = await pdfDoc // Add a blank page to the documentconst page = pdfDoc // Get the width and height of the pageconst width height = page // Draw a string of text toward the top of the pageconst fontSize = 30page // Serialize the PDFDocument to bytes (a Uint8Array)const pdfBytes = await pdfDoc // For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
Modify Document
This example produces this PDF (when this PDF is used for the existingPdfBytes
variable).
; // This should be a Uint8Array or ArrayBuffer// This data can be obtained in a number of different ways// If your running in a Node environment, you could use fs.readFile()// In the browser, you could make a fetch() call and use res.arrayBuffer()const existingPdfBytes = ... // Load a PDFDocument from the existing PDF bytesconst pdfDoc = await PDFDocument // Embed the Helvetica fontconst helveticaFont = await pdfDoc // Get the first page of the documentconst pages = pdfDocconst firstPage = pages0 // Get the width and height of the first pageconst width height = firstPage // Draw a string of text diagonally across the first pagefirstPage // Serialize the PDFDocument to bytes (a Uint8Array)const pdfBytes = await pdfDoc // For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
Copy Pages
This example produces this PDF (when this PDF is used for the firstDonorPdfBytes
variable and this PDF is used for the secondDonorPdfBytes
variable).
// Create a new PDFDocumentconst pdfDoc = await PDFDocument // These should be Uint8Arrays or ArrayBuffers// This data can be obtained in a number of different ways// If your running in a Node environment, you could use fs.readFile()// In the browser, you could make a fetch() call and use res.arrayBuffer()const firstDonorPdfBytes = ...const secondDonorPdfBytes = ... // Load a PDFDocument from each of the existing PDFsconst firstDonorPdfDoc = await PDFDocumentconst secondDonorPdfDoc = await PDFDocument // Copy the 1st page from the first donor document, and // the 743rd page from the second donor documentconst firstDonorPage = await pdfDocconst secondDonorPage = await pdfDoc // Add the first copied pagepdfDoc // Insert the second copied page to index 0, so it will be the // first page in `pdfDoc`pdfDoc // Serialize the PDFDocument to bytes (a Uint8Array)const pdfBytes = await pdfDoc // For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
Embed PNG and JPEG Images
This example produces this PDF (when this image is used for the jpgImageBytes
variable and this image is used for the pngImageBytes
variable).
// These should be Uint8Arrays or ArrayBuffers// This data can be obtained in a number of different ways// If your running in a Node environment, you could use fs.readFile()// In the browser, you could make a fetch() call and use res.arrayBuffer()const jpgImageBytes = ...const pngImageBytes = ... // Create a new PDFDocumentconst pdfDoc = await PDFDocument // Embed the JPG image bytes and PNG image bytesconst jpgImage = await pdfDocconst pngImage = await pdfDoc // Get the width/height of the JPG image scaled down to 25% of its original sizeconst jpgDims = jpgImage // Get the width/height of the PNG image scaled down to 50% of its original sizeconst pngDims = pngImage // Add a blank page to the documentconst page = pdfDoc // Draw the JPG image in the center of the pagepage // Draw the PNG image near the lower right corner of the JPG imagepage // Serialize the PDFDocument to bytes (a Uint8Array)const pdfBytes = await pdfDoc // For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
Embed PDF Pages
This example produces this PDF (when this PDF is used for the americanFlagPdfBytes
variable and this PDF is used for the usConstitutionPdfBytes
variable).
// These should be Uint8Arrays or ArrayBuffers// This data can be obtained in a number of different ways// If your running in a Node environment, you could use fs.readFile()// In the browser, you could make a fetch() call and use res.arrayBuffer()const americanFlagPdfBytes = ...const usConstitutionPdfBytes = ... // Create a new PDFDocumentconst pdfDoc = await PDFDocument // Embed the American flag PDF bytesconst americanFlag = await pdfDoc // Load the U.S. constitution PDF bytesconst usConstitutionPdf = await PDFDocument // Embed the second page of the constitution and clip the preambleconst preamble = await pdfDoc // Get the width/height of the American flag PDF scaled down to 30% of // its original sizeconst americanFlagDims = americanFlag // Get the width/height of the preamble clipping scaled up to 225% of // its original sizeconst preambleDims = preamble // Add a blank page to the documentconst page = pdfDoc // Draw the American flag image in the center top of the pagepage // Draw the preamble clipping in the center bottom of the pagepage // Serialize the PDFDocument to bytes (a Uint8Array)const pdfBytes = await pdfDoc // For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
Embed Font and Measure Text
pdf-lib
relies on a sister module to support embedding custom fonts: @pdf-lib/fontkit
. You must add the @pdf-lib/fontkit
module to your project and register it using pdfDoc.registerFontkit(...)
before embedding custom fonts.
This example produces this PDF (when this font is used for the fontBytes
variable).
// This should be a Uint8Array or ArrayBuffer// This data can be obtained in a number of different ways// If you're running in a Node environment, you could use fs.readFile()// In the browser, you could make a fetch() call and use res.arrayBuffer()const fontBytes = ... // Create a new PDFDocumentconst pdfDoc = await PDFDocument // Register the `fontkit` instancepdfDoc // Embed our custom font in the documentconst customFont = await pdfDoc // Add a blank page to the documentconst page = pdfDoc // Create a string of text and measure its width and height in our custom fontconst text = 'This is text in an embedded font!'const textSize = 35const textWidth = customFontconst textHeight = customFont // Draw the string of text on the pagepage // Draw a box around the string of textpage // Serialize the PDFDocument to bytes (a Uint8Array)const pdfBytes = await pdfDoc // For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
Add Attachments
This example produces this PDF (when this image is used for the jpgAttachmentBytes
variable and this PDF is used for the pdfAttachmentBytes
variable).
// These should be Uint8Arrays or ArrayBuffers// This data can be obtained in a number of different ways// If your running in a Node environment, you could use fs.readFile()// In the browser, you could make a fetch() call and use res.arrayBuffer()const jpgAttachmentBytes = ...const pdfAttachmentBytes = ... // Create a new PDFDocumentconst pdfDoc = await PDFDocument // Add the JPG attachmentawait pdfDoc // Add the PDF attachmentawait pdfDoc // Add a page with some textconst page = pdfDoc;page // Serialize the PDFDocument to bytes (a Uint8Array)const pdfBytes = await pdfDoc // For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
Set Document Metadata
This example produces this PDF.
// Create a new PDFDocumentconst pdfDoc = await PDFDocument // Embed the Times Roman fontconst timesRomanFont = await pdfDoc // Add a page and draw some text on itconst page = pdfDocpagepagepage // Set all available metadata fields on the PDFDocument. Note that these fields// are visible in the "Document Properties" section of most PDF readers.pdfDocpdfDocpdfDocpdfDocpdfDocpdfDocpdfDocpdfDoc // Serialize the PDFDocument to bytes (a Uint8Array)const pdfBytes = await pdfDoc // For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
Read Document Metadata
// This should be a Uint8Array or ArrayBuffer// This data can be obtained in a number of different ways// If your running in a Node environment, you could use fs.readFile()// In the browser, you could make a fetch() call and use res.arrayBuffer()const existingPdfBytes = ... // Load a PDFDocument without updating its existing metadataconst pdfDoc = await PDFDocument // Print all available metadata fieldsconsoleconsoleconsoleconsoleconsoleconsoleconsoleconsole
This script outputs the following (when this PDF is used for the existingPdfBytes
variable):
Title: Microsoft Word - Basic Curriculum Vitae example.doc
Author: Administrator
Subject: undefined
Creator: PScript5.dll Version 5.2
Keywords: undefined
Producer: Acrobat Distiller 8.1.0 (Windows)
Creation Date: 2010-07-29T14:26:00.000Z
Modification Date: 2010-07-29T14:26:00.000Z
Draw SVG Paths
This example produces this PDF.
// SVG path for a wavy lineconst svgPath = 'M 0,20 L 100,160 Q 130,200 150,120 C 190,-40 200,200 300,150 L 400,90' // Create a new PDFDocumentconst pdfDoc = await PDFDocument // Add a blank page to the documentconst page = pdfDocpage // Draw the SVG path as a black linepagepage // Draw the SVG path as a thick green linepagepage // Draw the SVG path and fill it with redpagepage // Draw the SVG path at 50% of its original sizepagepage // Serialize the PDFDocument to bytes (a Uint8Array)const pdfBytes = await pdfDoc // For example, `pdfBytes` can be:// • Written to a file in Node// • Downloaded from the browser// • Rendered in an <iframe>
Deno Usage
pdf-lib
fully supports the exciting new Deno runtime! All of the usage examples work in Deno. The only thing you need to do is change the imports for pdf-lib
and @pdf-lib/fontkit
to use the Pika CDN, because Deno requires all modules to be referenced via URLs.
See also How to Create and Modify PDF Files in Deno With pdf-lib
Creating a Document with Deno
Below is the create document example modified for Deno:
; const pdfDoc = await PDFDocument;const timesRomanFont = await pdfDoc; const page = pdfDoc;const width height = page;const fontSize = 30;page; const pdfBytes = await pdfDoc; await Deno;
If you save this script as create-document.ts
, you can execute it using Deno with the following command:
deno run --allow-write create-document.ts
The resulting out.pdf
file will look like this PDF.
Embedding a Font with Deno
Here's a slightly more complicated example demonstrating how to embed a font and measure text in Deno:
;; const url = 'https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf';const fontBytes = await ; const pdfDoc = await PDFDocument; pdfDoc;const customFont = await pdfDoc; const page = pdfDoc; const text = 'This is text in an embedded font!';const textSize = 35;const textWidth = customFont;const textHeight = customFont; page;page; const pdfBytes = await pdfDoc; await Deno;
If you save this script as custom-font.ts
, you can execute it with the following command:
deno run --allow-write --allow-net custom-font.ts
The resulting out.pdf
file will look like this PDF.
Complete Examples
The usage examples provide code that is brief and to the point, demonstrating the different features of pdf-lib
. You can find complete working examples in the apps/
directory. These apps are used to do manual testing of pdf-lib
before every release (in addition to the automated tests).
There are currently four apps:
node
- contains tests forpdf-lib
in Node environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images withpdf-lib
from the filesystem. They also allow you to quickly open your PDFs in different viewers (Acrobat, Preview, Foxit, Chrome, Firefox, etc...) to ensure compatibility.web
- contains tests forpdf-lib
in browser environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images withpdf-lib
in a browser environment.rn
- contains tests forpdf-lib
in React Native environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images withpdf-lib
in a React Native environment.deno
- contains tests forpdf-lib
in Deno environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images withpdf-lib
from the filesystem.
Installation
NPM Module
To install the latest stable version:
# With npm npm install --save pdf-lib # With yarn yarn add pdf-lib
This assumes you're using npm or yarn as your package manager.
UMD Module
You can also download pdf-lib
as a UMD module from unpkg or jsDelivr. The UMD builds have been compiled to ES5, so they should work in any modern browser. UMD builds are useful if you aren't using a package manager or module bundler. For example, you can use them directly in the <script>
tag of an HTML page.
The following builds are available:
- https://unpkg.com/pdf-lib/dist/pdf-lib.js
- https://unpkg.com/pdf-lib/dist/pdf-lib.min.js
- https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.js
- https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.min.js
NOTE: if you are using the CDN scripts in production, you should include a specific version number in the URL, for example:
When using a UMD build, you will have access to a global window.PDFLib
variable. This variable contains all of the classes and functions exported by pdf-lib
. For example:
// NPM module; // UMD modulevar PDFDocument = PDFLibPDFDocument;var rgb = PDFLibrgb;
Fontkit Installation
pdf-lib
relies upon a sister module to support embedding custom fonts: @pdf-lib/fontkit
. You must add the @pdf-lib/fontkit
module to your project and register it using pdfDoc.registerFontkit(...)
before embedding custom fonts (see the font embedding example). This module is not included by default because not all users need it, and it increases bundle size.
Installing this module is easy. Just like pdf-lib
itself, @pdf-lib/fontkit
can be installed with npm
/yarn
or as a UMD module.
Fontkit NPM Module
# With npm npm install --save @pdf-lib/fontkit # With yarn yarn add @pdf-lib/fontkit
To register the fontkit
instance:
const pdfDoc = await PDFDocumentpdfDoc
Fontkit UMD Module
The following builds are available:
- https://unpkg.com/@pdf-lib/fontkit/dist/fontkit.umd.js
- https://unpkg.com/@pdf-lib/fontkit/dist/fontkit.umd.min.js
- https://cdn.jsdelivr.net/npm/@pdf-lib/fontkit/dist/fontkit.umd.js
- https://cdn.jsdelivr.net/npm/@pdf-lib/fontkit/dist/fontkit.umd.min.js
NOTE: if you are using the CDN scripts in production, you should include a specific version number in the URL, for example:
When using a UMD build, you will have access to a global window.fontkit
variable. To register the fontkit
instance:
var pdfDoc = await PDFLibPDFDocumentpdfDoc
Documentation
API documentation is available on the project site at https://pdf-lib.js.org/docs/api/.
The repo for the project site (and generated documentation files) is located here: https://github.com/Hopding/pdf-lib-docs.
Encryption Handling
pdf-lib
does not currently support modification of encrypted documents. In general, it is not advised to use pdf-lib
with encrypted documents. However, this is a feature that could be added to pdf-lib
. Please create an issue if you would find this feature helpful!
When an encrypted document is passed to PDFDocument.load(...)
, an error will be thrown:
const encryptedPdfBytes = ... // Assignment fails. Throws an `EncryptedPDFError`.const pdfDoc = PDFDocument
This default behavior is usually what you want. It allows you to easily detect if a given document is encrypted, and it prevents you from trying to modify it. However, if you really want to load the document, you can use the { ignoreEncryption: true }
option:
const encryptedPdfBytes = ... // Assignment succeeds. Does not throw an error.const pdfDoc = PDFDocument
Note that using this option does not decrypt the document. This means that any modifications you attempt to make on the returned PDFDocument
may fail, or have unexpected results.
Migrating to v1.0.0
The latest release of pdf-lib
(v1.0.0
) includes several breaking API changes. If you have code written for older versions of pdf-lib
(v0.x.x
), you can use the following instructions to help migrate your code to v1.0.0.
Note that many of the API methods are now asynchronous and return promises, so you'll need to await
on them (or use promise chaining: .then(res => ...)
).
- Rename
PDFDocumentFactory
toPDFDocument
.PDFDocument.create
andPDFDocument.load
are now async (they return promises), so you'll need toawait
on them.
-
To create a new PDF document:
const pdfDoc = await PDFDocument; -
To retrieve and load a PDF where
pdfUrl
points to the PDF to be loaded:const pdfBuffer = await ;const pdfDoc = await PDFDocument;
-
The purpose of making these methods asynchronous is to avoid blocking the event loop (especially for browser-based usage). If you aren't running this code client-side and are not concerned about blocking the event loop, you can speed up parsing times with:
PDFDocumentYou can do a similar thing for save:
PDFDocument; -
To draw content on a page in old versions of
pdf-lib
, you needed to create a content stream, invoke some operators, register the content stream, and add it to the document. Something like the following:const contentStream = pdfDoc;page;However, in new versions of
pdf-lib
, this is much simpler. You simply invoke drawing methods on the page, such asPDFPage.drawText
,PDFPage.drawImage
,PDFPage.drawRectangle
, orPDFPage.drawSvgPath
. So the above example becomes:page;Please see the Usage Examples for more in depth examples of drawing content on a page in the new versions of
pdf-lib
. You may also find the Complete Examples to be a useful reference. -
Change
getMaybe
function calls toget
calls. If a property doesn't exist, thenundefined
will be returned. Note, however, that PDF name strings with need to be wrapped inPDFName.of(...)
. For example, to look up the AcroForm object you'll need to changepdfDoc.catalog.getMaybe('AcroForm')
topdfDoc.catalog.get(PDFName.of('AcroForm'))
.const acroForm = await pdfDoccontext;v0.x.x converted the strings passed to
get
andgetMaybe
toPDFName
objects, but v1.0.0 does not do this conversion for you. So you must always pass actualPDFName
objects instead of strings. -
To find the AcroForm field references now becomes:
const acroFieldRefs = await pdfDoccontext; -
To add a new page replace
pdfDoc.createPage([width, height])
withpdfDoc.addPage([width, height])
const page = pdfDoc;or simply:
const page = pdfDoc;
-
To get the size of the page:
const width height = page;page;page; -
To add images replace
pdfDoc.embedPNG
withpdfDoc.embedPng
andpdfDoc.embedJPG
withpdfDoc.embedJpg
-
The
pdfDoc.embedPng
andpdfDoc.embedJpg
methods now returnPDFImage
objects which have the width and height of the image as properties. You can also scale down the width and height by a constant factor using thePDFImage.scale
method:const aBigImage = await pdfDoc;const width height = aBigImage;So,
const [image, dims] = pdfDoc.embedJPG(mediaBuffer)
becomes:const image = await pdfDoc;// image.width, image.height can be used instead of the dims object. -
To save the PDF replace
PDFDocumentWriter.saveToBytes(pdfDoc)
withpdfDoc.save()
const pdfDocBytes = await pdfDoc; -
To display the saved PDF now becomes:
const pdfUrl = URL;window;(note:
URL.revokeObjectURL
should be called later to free up memory) -
To get the PDF page count:
pdfDoclength; -
To copy pages from one document to another you must now call
destPdf.copyPages(srcPdf, srcPageIndexesArray)
to copy pages. You can see an example of this in the Copy Pages usage example. Admittedly, this API is slightly less ergonomic than what exists in v0.x.x, but it has two key benefits:-
It avoids making PDFDocument.addPage and PDFDocument.insertPage async. When copying multiple pages from the source document, the resulting merged document should have a smaller file size. This is because the page copying API that exists in v0.x.x was intended for copying just one or two pages.
-
When copying large numbers of pages, it could result in redundant objects being created. This new page copying API should eliminate that.
{const mergedPdf = await PDFDocument;for const pdfCopyDoc of pdfsToMergeconst pdfBytes = fs;const pdf = await PDFDocument;const copiedPages = await mergedPdf;copiedPages;const mergedPdfFile = await mergedPdf;return mergedPdfFile;} -
-
If required, you can retrieve the CropBox or MediaBox of a page like so:
const cropBox = pagenode || pagenode;
Contributing
We welcome contributions from the open source community! If you are interested in contributing to pdf-lib
, please take a look at the CONTRIBUTING.md file. It contains information to help you get pdf-lib
setup and running on your machine. (We try to make this as simple and fast as possible! 🚀)
Tutorials and Cool Stuff
- Möbius Printing helper - a tool created by @shreevatsa
- Extract PDF pages - a tool created by @shreevatsa
- Travel certificate generator - a tool that creates travel certificates for French citizens under quarantine due to COVID-19
- How to use pdf-lib in AWS Lambdas - a tutorial written by Crespo Wang
- Working With PDFs in Node.js Using pdf-lib - a tutorial by Valeri Karpov
Prior Art
pdfkit
is a PDF generation library for Node and the Browser. This library was immensely helpful as a reference and existence proof when creatingpdf-lib
.pdfkit
's code for font embedding, PNG embedding, and JPG embedding was especially useful.pdf.js
is a PDF rendering library for the Browser. This library was helpful as a reference when writingpdf-lib
's parser. Some of the code for stream decoding was ported directly to TypeScript for use inpdf-lib
.jspdf
is a PDF generation library for the browser.pdfmake
is a PDF generation library for the browser.hummus
is a PDF generation and modification library for Node environments.hummus
is a Node wrapper around a C++ library, so it doesn't work in many JavaScript environments - like the Browser or React Native.react-native-pdf-lib
is a PDF generation and modification library for React Native environments.react-native-pdf-lib
is a wrapper around C++ and Java libraries.pdfassembler
is a PDF generation and modification library for Node and the browser. It requires some knowledge about the logical structure of PDF documents to use.