Blog Post

How to build a JavaScript PDF editor with pdf-lib

Veronica Marini
Hulya Masharipov
Illustration: How to build a JavaScript PDF editor with pdf-lib
Information

This article was first published in September 2021 and was updated in November 2024.

In this tutorial, we’ll guide you through building a simple JavaScript PDF editor with pdf-lib. Then, we’ll show how to deploy your editor in Nutrient’s JavaScript PDF viewer.

If you prefer a video walkthrough, check out our step-by-step guide:

Introduction to pdf-lib

pdf-lib is a versatile JavaScript library for creating and modifying PDF documents in any JavaScript environment, including browsers, Node.js, Deno, and React Native. With pdf-lib, you can create PDFs from scratch, modify existing PDFs, and perform various operations such as adding text, images, and vector graphics. This makes it ideal for adding PDF functionality to web applications.

Choosing the Right JavaScript PDF Editor

For open-source options, pdf-lib is a solid choice. Its primary features include:

  • Support for modifying existing documents

  • Compatibility across all JavaScript environments

For commercial needs, Nutrient’s JavaScript PDF editor offers enhanced functionality:

  • Customizable, out-of-the-box UI for quick deployment

  • Syncing framework to save edits across devices

  • Advanced features like annotations, redaction, and digital signatures

  • Support for editing Word, Excel, and PowerPoint documents

  • Access to dedicated customer support

Building a Simple PDF Editor with pdf-lib

This tutorial will cover setting up a basic PDF editor that can add, remove, and draw text on pages.

Initial Setup

Create a basic HTML structure to initialize the 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>

Loading and Rendering the Document

With pdf-lib, you can load an existing PDF or create a new one. For this tutorial, we’ll load an existing PDF from the project folder.

const { PDFDocument } = PDFLib;

let pdfDoc;

async function loadPdf() {
	const url = './demo.pdf';
	const existingPdfBytes = await fetch(url).then((res) =>
		res.arrayBuffer(),
	);
	return PDFDocument.load(existingPdfBytes);
}

async function saveAndRender(doc) {
	const pdfBytes = await doc.save();
	const pdfDataUri = await doc.saveAsBase64({ dataUri: true });
	document.getElementById('pdf').src = pdfDataUri;
}

Adding and Removing Pages

Create buttons to add or remove pages:

<div>
	<button onclick="addPage()">Add page</button>
	<button onclick="removePage()">Remove page</button>
</div>

Then add these 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);
}

Drawing Text on Pages

To add text to a page, use drawText() from pdf-lib:

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;
}

Embedding Images and Fonts

To embed images, use embedJpg or embedPng with the image data:

async function embedImage() {
	const imageBytes = await fetch('image.png').then((res) =>
		res.arrayBuffer(),
	);
	const image = await pdfDoc.embedPng(imageBytes);
	const page = pdfDoc.addPage();
	page.drawImage(image, { x: 100, y: 100, width: 200, height: 150 });
	await saveAndRender();
}

You can also embed fonts with embedFont:

const font = await pdfDoc.embedFont(StandardFonts.Helvetica);

Filling Form Fields

To fill form fields in existing PDFs, retrieve the form fields and set values:

async function fillForm() {
	const formPdfBytes = await fetch('form.pdf').then((res) =>
		res.arrayBuffer(),
	);
	pdfDoc = await PDFDocument.load(formPdfBytes);

	const form = pdfDoc.getForm();
	const nameField = form.getTextField('name');
	nameField.setText('Jane Doe');

	await saveAndRender(pdfDoc);
}

Setting Document Metadata

You can set metadata like title, author, and subject:

async function setMetadata() {
	pdfDoc.setTitle('My PDF Document');
	pdfDoc.setAuthor('John Doe');
	pdfDoc.setSubject('This is a sample PDF document');
	await saveAndRender(pdfDoc);
}

This is how your final HTML and JavaScript structure should look:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta
			name="viewport"
			content="width=device-width, initial-scale=1.0"
		/>
		<title>PDF Editor</title>
		<script src="https://unpkg.com/[email protected]/dist/pdf-lib.min.js"></script>
	</head>
	<body>
		<h1>PDF Editor</h1>
		<button onclick="addPage()">Add Page</button>
		<button onclick="removePage()">Remove Page</button>
		<button onclick="embedImage()">Embed Image</button>
		<button onclick="fillForm()">Fill Form</button>
		<button onclick="setMetadata()">Set Metadata</button>
		<iframe id="pdfViewer" width="100%" height="600"></iframe>

		<script>
			let pdfDoc;
			const pdfViewer = document.getElementById('pdfViewer');

			// Load an existing PDF document on initialization
			async function loadPdf() {
				const existingPdfBytes = await fetch(
					'demo.pdf',
				).then((res) => res.arrayBuffer());
				pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes);
				await saveAndRender();
			}

			// Save and display the PDF in the iframe
			async function saveAndRender() {
				const pdfBytes = await pdfDoc.saveAsBase64({
					dataUri: true,
				});
				pdfViewer.src = pdfBytes;
			}

			// Add a new page with text
			async function addPage() {
				const page = pdfDoc.addPage();
				const font = await pdfDoc.embedFont(
					PDFLib.StandardFonts.Helvetica,
				);
				page.drawText('New Page', {
					x: 50,
					y: 700,
					font,
					size: 24,
				});
				await saveAndRender();
			}

			// Remove the last page
			async function removePage() {
				if (pdfDoc.getPageCount() > 1) {
					// Ensure at least one page remains
					pdfDoc.removePage(pdfDoc.getPageCount() - 1);
					await saveAndRender();
				} else {
					alert('Cannot remove the last page!');
				}
			}

			// Embed an image into the PDF
			async function embedImage() {
				const imageBytes = await fetch('image.png').then((res) =>
					res.arrayBuffer(),
				);
				const image = await pdfDoc.embedPng(imageBytes);
				const page = pdfDoc.addPage();
				page.drawImage(image, {
					x: 100,
					y: 100,
					width: 200,
					height: 150,
				});
				await saveAndRender();
			}

			// Fill out form fields (assuming the PDF has form fields)
			async function fillForm() {
				if (!pdfDoc) {
					alert(
						'Load a PDF with form fields to use this feature.',
					);
					return;
				}
				try {
					const form = pdfDoc.getForm();
					const nameField = form.getTextField('name'); // Change 'name' to your form field name
					nameField.setText('Jane Doe');
					await saveAndRender();
				} catch (e) {
					console.error('Form filling error:', e);
					alert(
						'Error filling form. Make sure the PDF has the correct form fields.',
					);
				}
			}

			// Set metadata for the PDF document
			async function setMetadata() {
				pdfDoc.setTitle('My PDF Document');
				pdfDoc.setAuthor('John Doe');
				pdfDoc.setSubject('This is a sample PDF document');
				await saveAndRender();
			}

			// Initial load
			loadPdf();
		</script>
	</body>
</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:

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.

Authors
Hulya Masharipov Technical Writer

Hulya is a frontend web developer and technical writer at Nutrient who enjoys creating responsive, scalable, and maintainable web experiences. She’s passionate about open source, web accessibility, cybersecurity privacy, and blockchain.

Veronica Marini Web Engineer

Veronica’s passion for puzzles got her into programming. She likes everything frontend, bringing design to life, and measuring herself with coding. She also collects hobbies: from yoga to surfing to playing Brazilian drums.

Related products
Share post
Free trial Ready to get started?
Free trial