How to build a JavaScript PDF editor with pdf-lib
In the first part of this tutorial, we’ll show how to build a simple free JavaScript PDF editor using pdf-lib
. In the second part, we’ll walk through how you can deploy PDF editing in Nutrient’s JavaScript PDF viewer.
If you prefer a video tutorial, you can watch our step-by-step guide:
Choosing the right JavaScript PDF editor
If you’re looking for an open source PDF-processing library, pdf-lib
is a great option. According to its GitHub page, pdf-lib
’s main features are that it:
-
Supports modification (editing) of existing documents.
-
Works in all JavaScript environments.
If you’re looking for a commercial JavaScript PDF editor, our Web SDK offers some additional benefits:
-
A customizable out-of-the-box UI to deploy faster and save developer resources.
-
The ability to persist your PDF edits and changes across devices with a syncing framework like Nutrient Instant.
-
More features — like annotating and collaborating on PDFs, redaction, and digital signatures.
-
View and edit Word, Excel, and PowerPoint documents inside the Nutrient viewer, without any server processing required.
-
Access to dedicated customer support to help speed up deployment.
So now, let’s move on to building a simple PDF editor that adds and removes pages and draws text on a document.
Building a PDF editor with pdf-lib
This section will cover how you can use pdf-lib
to create and integrate a PDF editor into your project.
Initial setup
Before diving into the editor implementation, we’ll lay out a simple HTML skeleton to initialize our project:
<html> <head> <meta charset="utf-8" /> <script src="https://unpkg.com/[email protected]"></script> <script src="https://unpkg.com/[email protected]"></script> </head> <body> <iframe id="pdf" style="width: 90%; height: 90%;"></iframe> </body> </html>
As you can see, we’re using the hosted version from unpkg, but you could also download the pdf-lib
library via a package manager like npm or Yarn.
Loading and rendering the document
With pdf-lib
, you can either create a new document or modify an existing one. For our example, we’ll load an existing document from our project folder. Please note that the PDF needs to be embedded in an iframe
tag.
Here’s how we load our document:
const { PDFDocument } = PDFLib; let pdfDoc; async function loadPdf() { // Fetch an existing PDF document. const url = './demo.pdf'; const existingPdfBytes = await fetch(url).then((res) => res.arrayBuffer(), ); // Load a `PDFDocument` from the existing PDF bytes. return PDFDocument.load(existingPdfBytes); }
And here’s how we render it:
async function saveAndRender(doc) { // Serialize the `PDFDocument` to bytes (a `Uint8Array`). const pdfBytes = await doc.save(); const pdfDataUri = await doc.saveAsBase64({ dataUri: true }); document.getElementById('pdf').src = pdfDataUri; }
Adding and removing pages
To add or remove the pages, we create two buttons:
<body> <div> <button onclick="addPage()">Add page</button> <button onclick="removePage()">Remove page</button> </div> <iframe id="pdf" style="width: 90%; height: 90%;"></iframe> </body>
Every time they’re clicked, they each call one of these two functions:
async function addPageToDoc(doc) { doc.addPage(); return doc; } async function removePageToDoc(doc) { const totalPages = doc.getPageCount(); doc.removePage(totalPages - 1); return doc; } async function addPage() { pdfDoc = await addPageToDoc(pdfDoc); await saveAndRender(pdfDoc); } async function removePage() { pdfDoc = await removePageToDoc(pdfDoc); await saveAndRender(pdfDoc); }
Our buttons add and remove pages at the end of the document, but if, for example, you’d like to add a page at a specific page index, you can use the .insertPage(index, page?)
method instead of addPage()
.
Drawing text
Now let’s say we’d like to draw some text on the added pages. pdf-lib
exposes a drawText()
API, so we can achieve this by adding few lines of code to our addPageToDoc
function:
async function addPageToDoc(doc) { const page = doc.addPage(); const timesRomanFont = await pdfDoc.embedFont( StandardFonts.TimesRoman, ); const { width, height } = page.getSize(); const fontSize = 30; page.drawText('Adding a page in JavaScript is awesome!', { x: 50, y: height - 4 * fontSize, size: fontSize, font: timesRomanFont, color: rgb(0, 0.53, 0.71), }); return doc; }
This is how our code will look in the end:
<html> <head> <meta charset="utf-8" /> <script src="https://unpkg.com/[email protected]"></script> <script src="https://unpkg.com/[email protected]"></script> </head> <body> <div> <button onclick="addPage()">Add page</button> <button onclick="removePage()">Remove page</button> </div> <iframe id="pdf" style="width: 90%; height: 90%;"></iframe> </body> <script> const { degrees, PDFDocument, rgb, StandardFonts } = PDFLib let pdfDoc; async function loadPdf() { // Fetch an existing PDF document. const url = './demo.pdf' const existingPdfBytes = await fetch(url).then(res => res.arrayBuffer()) // Load a `PDFDocument` from the existing PDF bytes. return await PDFDocument.load(existingPdfBytes) } async function addPageToDoc(doc) { const page = doc.addPage() const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman) const { width, height } = page.getSize() const fontSize = 30 page.drawText('Adding a page in JavaScript is awesome!', { x: 50, y: height - 4 * fontSize, size: fontSize, font: timesRomanFont, color: rgb(0, 0.53, 0.71), }) return doc; } async function removePageToDoc(doc) { const totalPages = doc.getPageCount() doc.removePage(totalPages - 1) return doc; } async function saveAndRender(doc) { // Serialize the `PDFDocument` to bytes (a `Uint8Array`). const pdfBytes = await doc.save() const pdfDataUri = await doc.saveAsBase64({ dataUri: true }); document.getElementById('pdf').src = pdfDataUri; } async function addPage() { pdfDoc = await addPageToDoc(pdfDoc); await saveAndRender(pdfDoc); } async function removePage() { pdfDoc = await removePageToDoc(pdfDoc); await saveAndRender(pdfDoc) } loadPdf().then((doc) => { pdfDoc = doc; return saveAndRender(pdfDoc); }) </script> </html>
Integrating Nutrient’s JavaScript PDF editor
This next section will cover how to integrate Nutrient’s JavaScript PDF editor into your project.
Adding Nutrient to your project
First, you need to have:
Nutrient Web SDK library files are distributed as an archive that can be installed as an npm module by running:
yarn add pspdfkit
Copy the Nutrient Web SDK distribution to the assets directory in your project’s folder:
cp -R ./node_modules/pspdfkit/dist/ ./assets/
Make sure your assets directory contains the pspdfkit.js
file and a pspdfkit-lib
directory with the library assets.
Integrating into your project
Add the PDF document you want to display to your project’s directory. For a quick test, you can use our the License.pdf
document, which you’ll find in the Nutrient Web SDK directory.
Now, let’s lay out another simple HTML skeleton to initialize our project:
<!DOCTYPE html> <html> <head> <title>My App</title> <!-- Provide proper viewport information so that the layout works on mobile devices. --> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" /> </head> <body></body> </html>
Add an empty <div>
element with a defined width and height to where Nutrient will be mounted:
<div id="pspdfkit" style="width: 100%; height: 100vh;"></div>
Import pspdfkit
into your application and initialize Nutrient Web SDK in JavaScript by calling PSPDFKit.load()
:
import './assets/pspdfkit.js'; // We need to inform Nutrient where to look for its library assets, i.e. the location of the `pspdfkit-lib` directory. const baseUrl = `${window.location.protocol}//${window.location.host}/assets/`; PSPDFKit.load({ baseUrl, container: '#pspdfkit', document: 'path/to/document.pdf', }) .then((instance) => { console.log('PSPDFKit loaded', instance); }) .catch((error) => { console.error(error.message); });
Import index.js
into your HTML page:
<script type="module" src="index.js"></script>
Here’s how the final HTML file will look:
<!DOCTYPE html> <html> <head> <title>My App</title> <!-- Provide proper viewport information so that the layout works on mobile devices. --> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" /> </head> <body> <!-- Element where PSPDFKit will be mounted. --> <div id="pspdfkit" style="width: 100%; height: 100vh;"></div> <script type="module" src="index.js"></script> </body> </html>
The last thing left to do is serve your website.
We’ll use the npm serve
package as a simple HTTP server.
-
Install the
serve
package:
yarn global add serve
-
Serve the contents of the current directory:
serve -l 8080 .
Open http://localhost:8080 in your browser to view the website.
And that’s it! All the features we built using pdf-lib
are already present out of the box in our SDK, so we don’t need to do anything else.
Conclusion
pdf-lib
is a good free JavaScript PDF editor option for modifying a PDF document, and it’s a great choice in many cases. However, implementing a feature-rich editor isn’t a trivial task. And sometimes businesses require more complex features, such as:
-
An annotation syncing framework to persist changes to a PDF across devices.
-
The ability to add and read digital signatures with specialized APIs.
-
An easy-to-integrate UI that allows you to customize every aspect of the viewer.
At Nutrient, we offer a commercial, feature-rich, and completely customizable JavaScript PDF library that’s easy to integrate and comes with well-documented APIs to handle advanced use cases. Check out our demo to see it in action.
FAQ
Here are a few frequently asked questions about building a JavaScript PDF editor.
How can I build a PDF editor using JavaScript?
You can build a PDF editor using JavaScript by leveraging libraries like PDF-lib, Nutrient, or PDF.js. These libraries provide APIs to create, modify, and interact with PDF documents.
What are the basic steps to create a PDF editor in JavaScript?
Install the chosen library using npm or a CDN, initialize the library in your JavaScript code, and use its methods to load, edit, and save PDF documents. You can add features like text editing, annotations, and form filling.
Can I add annotations to PDFs using a JavaScript PDF editor?
Yes, most PDF libraries support adding annotations such as highlights, comments, and shapes. You can use the library’s API to create and manipulate these annotations.
What are the benefits of using a JavaScript-based PDF editor?
A JavaScript-based PDF editor allows for client-side processing, reducing the need for server resources. It also provides a seamless user experience by enabling PDF editing directly within the browser.
What are some common challenges when building a PDF editor with JavaScript?
Common challenges include handling large PDF files, ensuring cross-browser compatibility, maintaining performance, and providing a user-friendly interface for editing complex documents.