Blog Post

How to Build a Node.js PDF Editor with pdf-lib

Illustration: How to Build a Node.js PDF Editor with pdf-lib

In this tutorial, you’ll explore how to use the pdf-lib library in Node.js to edit PDFs. It’ll cover various functionalities, including reading and writing PDF files, modifying existing PDFs, copying pages from different PDFs, and embedding images into PDF documents.

In the second part of the tutorial, you’ll learn about PSPDFKit API as an alternative solution for PDF editing. Leveraging the power of PSPDFKit API, you can efficiently manipulate PDF documents, streamline workflows, and enhance document management processes.

Comparing PSPDFKit API and pdf-lib

When choosing a Node.js PDF editor, it’s important to consider the available options and compare their features. The following sections will summarize the key points of PSPDFKit API and pdf-lib to help you make an informed decision.

PSPDFKit API

PSPDFKit API is a hosted service that provides more than 30 tools for developers to build automated document processing workflows. Key benefits include:

  • Security — PSPDFKit API is SOC 2-compliant, ensuring the highest level of security for your document data. It doesn’t store any document data, and API endpoints are served through encrypted connections.

  • Easy Integration — PSPDFKit API provides well-documented APIs and code samples, making it easy to integrate into your existing workflows. The seamless integration saves you time and effort in development.

  • Versatile Actions — With PSPDFKit API, you have access to more than 30 tools and functionalities. You can perform various operations on a single document, such as conversion, OCR, rotation, watermarking, and more, with a single API call.

  • Transparent Pricing — PSPDFKit API offers transparent pricing based on the number of documents processed. It eliminates the need to consider factors like file size, merged datasets, or different API actions, providing straightforward pricing.

pdf-lib

  • Modification of Existing Documentspdf-lib specializes in modifying (editing) existing PDF documents, allowing you to add text, images, and more to your PDFs.

  • Wide JavaScript Environment Compatibilitypdf-lib works in all JavaScript environments, providing flexibility in usage.

  • Open Sourcepdf-lib is an open source library, which means it’s free to use and has an active community contributing to its development.

  • Suitable for Basic Editing Needspdf-lib is a good option for basic PDF editing requirements, but it may not have all the advanced features and capabilities offered by a commercial solution like PSPDFKit API.

Both PSPDFKit API and pdf-lib have their strengths and are suitable for different use cases. PSPDFKit API offers convenience and performance when processing documents, while pdf-lib is a great open source project for simple PDF editing or document modifications.

Prerequisites for Building a pdf-lib and Node.js PDF Editor

Before you dive into building your Node.js PDF editor, ensure you have the following prerequisites:

  • Basic knowledge of JavaScript and Node.js.

  • Node.js installed on your system.

  • A text editor of your choice.

  • A package manager for installing packages. You can use npm or Yarn.

Setting Up the Project

You’ll begin by setting up a new Node.js project. Open your terminal and follow the steps outlined below.

  1. Create a new directory for your project:

mkdir pdf-editor
cd pdf-editor
  1. Initialize a new Node.js project and install pdf-lib and axios:

npm init -y
npm install pdf-lib axios
  1. Create a new JavaScript file, e.g. index.js, and open it in your preferred code editor.

  2. Import the necessary dependencies:

const { PDFDocument, rgb, StandardFonts, degrees } = require('pdf-lib');
const fs = require('fs');
const axios = require('axios');

With the project set up, you can now implement the PDF editing functionality.

Reading and Writing PDF Files

Before you can edit a PDF, you need the ability to read existing PDF files and write modified PDFs to disk. You’ll create two functions — readPDF and writePDF — to handle these tasks:

async function readPDF(path) {
	const file = await fs.promises.readFile(path);
	return await PDFDocument.load(file);
}

async function writePDF(pdf, path) {
	const pdfBytes = await pdf.save();
	await fs.promises.writeFile(path, pdfBytes);
	console.log('PDF saved successfully!');
}

The readPDF function loads a PDF document from a file path and returns a PDFDocument object. Meanwhile, the writePDF function takes a PDFDocument and a file path as parameters, saves the modified PDF, and writes it to the specified path.

To demonstrate the use, you’ll load an existing PDF document, access its content, and save it as a new file:

(async () => {
	const pdf = await readPDF('input.pdf');
	console.log(pdf); // Access and use the loaded PDF object.

	// Perform modifications on the PDF document.
	const pdfDoc = await PDFDocument.create();
	const page = pdfDoc.addPage();
	page.drawText('Hello, PDF!');

	await writePDF(pdf, 'output.pdf');
})();

Modifying PDFs: Adding Text with JavaScript

The pdf-lib library allows you to modify existing PDF documents by adding various elements such as text, images, and more. In this section, you’ll see an example of how to add text to a PDF document using pdf-lib.

Adding Text to a PDF

To add text to a PDF, use pdf-lib library’s PDFDocument API:

async function modifyPdf() {
	const filePath = 'input.pdf';
	const existingPdfBytes = fs.readFileSync(filePath);

	const pdfDoc = await PDFDocument.load(existingPdfBytes);
	const helveticaFont = await pdfDoc.embedFont(
		StandardFonts.Helvetica,
	);

	const pages = pdfDoc.getPages();
	const firstPage = pages[0];
	const { width, height } = firstPage.getSize();

	firstPage.drawText('This text was added with JavaScript!', {
		x: 5,
		y: height / 2 + 300,
		size: 50,
		font: helveticaFont,
		color: rgb(0.95, 0.1, 0.1),
		rotate: degrees(-45),
	});

	const pdfBytes = await pdfDoc.save();

	fs.writeFileSync('modified.pdf', pdfBytes);
	console.log('Modified PDF saved successfully!');
}

// Usage.
modifyPdf();

In this example, you start by loading an existing PDF document from a local file (input.pdf). You then embed the Helvetica font using pdfDoc.embedFont to ensure it’s available for use. Next, you access the first page of the PDF using pdfDoc.getPages()[0] and obtain its width and height. Using the drawText method, you add a text annotation to the page, specifying the position, font, size, color, and rotation.

Once the modifications are complete, you save the modified PDF as modified.pdf using pdfDoc.save(), and a success message is logged to the console.

Embedding Images into PDFs

In addition to text, you can also add images to a PDF using pdf-lib:

async function embedImages() {
	const jpgUrl =
		'https://pdf-lib.js.org/assets/cat_riding_unicorn.jpg';
	const pngUrl =
		'https://pdf-lib.js.org/assets/minions_banana_alpha.png';

	const jpgResponse = await axios.get(jpgUrl, {
		responseType: 'arraybuffer',
	});
	const pngResponse = await axios.get(pngUrl, {
		responseType: 'arraybuffer',
	});

	const jpgImageBytes = jpgResponse.data;
	const pngImageBytes = pngResponse.data;

	const pdfDoc = await PDFDocument.create();

	const jpgImage = await pdfDoc.embedJpg(jpgImageBytes);
	const pngImage = await pdfDoc.embedPng(pngImageBytes);

	const jpgDims = jpgImage.scale(0.5);
	const pngDims = pngImage.scale(0.5);

	const page = pdfDoc.addPage();

	page.drawImage(jpgImage, {
		x: page.getWidth() / 2 - jpgDims.width / 2,
		y: page.getHeight() / 2 - jpgDims.height / 2 + 250,
		width: jpgDims.width,
		height: jpgDims.height,
	});

	page.drawImage(pngImage, {
		x: page.getWidth() / 2 - pngDims.width / 2 + 75,
		y: page.getHeight() / 2 - pngDims.height + 250,
		width: pngDims.width,
		height: pngDims.height,
	});

	const pdfBytes = await pdfDoc.save();
	fs.writeFileSync('output3.pdf', pdfBytes);
	console.log('PDF saved successfully!');
}

embedImages();

In this example, you use axios to fetch two image files: a JPEG image (cat_riding_unicorn.jpg), and a PNG image (minions_banana_alpha.png). You create a new PDF document and embed the JPEG and PNG images into it. The images are scaled down by 50 percent and placed on a new page. The modified PDF is saved as output3.pdf.

Copying Pages from Multiple PDFs

Another powerful feature of the pdf-lib library is the ability to extract pages from different PDF documents and combine them into a new document:

async function copyPages() {
	const url1 =
		'https://pdf-lib.js.org/assets/with_update_sections.pdf';
	const url2 =
		'https://pdf-lib.js.org/assets/with_large_page_count.pdf';

	const firstDonorPdfResponse = await axios.get(url1, {
		responseType: 'arraybuffer',
	});
	const secondDonorPdfResponse = await axios.get(url2, {
		responseType: 'arraybuffer',
	});

	const firstDonorPdfBytes = firstDonorPdfResponse.data;
	const secondDonorPdfBytes = secondDonorPdfResponse.data;

	const firstDonorPdfDoc = await PDFDocument.load(firstDonorPdfBytes);
	const secondDonorPdfDoc = await PDFDocument.load(
		secondDonorPdfBytes,
	);

	const pdfDoc = await PDFDocument.create();

	const [firstDonorPage] = await pdfDoc.copyPages(firstDonorPdfDoc, [
		0,
	]);
	const [secondDonorPage] = await pdfDoc.copyPages(secondDonorPdfDoc, [
		742,
	]);

	pdfDoc.addPage(firstDonorPage);
	pdfDoc.insertPage(0, secondDonorPage);

	const pdfBytes = await pdfDoc.save();

	fs.writeFileSync('output2.pdf', pdfBytes);
	console.log('PDF saved successfully!');
}

copyPages();

In this example, you fetch two PDF documents (with_update_sections.pdf and with_large_page_count.pdf) from remote URLs using axios. You load the PDF data using the PDFDocument.load method from pdf-lib to create PDFDocument objects for each donor PDF. Then, using the copyPages method, you combine specific pages from the donor PDFs into a new PDF. The modified PDF is saved as output2.pdf.

Integrating with PSPDFKit API

PSPDFKit API is an HTTP API that provides powerful features for manipulating PDF files. With the PDF Editor API, you can perform various actions, such as merging, splitting, deleting, flattening, and duplicating PDF documents. Here are some key functionalities:

  • Merge — Combine multiple PDF files into a single document using the merge PDF API. This allows you to merge large files efficiently.

  • Split — Select specific pages from an existing PDF file and create a new document using the split PDF API.

  • Delete — Remove one or multiple pages from a PDF using the PDF page deletion API.

  • Flatten — Flatten annotations in single or multiple PDF files with the flatten PDF API, ensuring a simplified viewing experience.

  • Duplicate — Create a copy of a page within a PDF document using the duplicate PDF page API.

Requirements

To get started, you’ll need:

You also need to install the following dependencies for all the examples:

  • axios — This package is used for making REST API calls.

  • Form-Data — This package is used for creating form data.

npm install form-data axios

To access your PSPDFKit API key, sign up for a free account. Your account lets you generate 100 documents for free every month. Once you’ve signed up, you can find your API key in the Dashboard > API Keys section.

Merging PDFs Using PSPDFKit API

In this example, learn how to merge multiple PDF files using the Merge PDF API.

  1. Create a new folder named merge_pdf and open it in a code editor.

  2. Create a new file named processor.js in the root of the folder. Copy the following code and paste it into the file:

const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

const formData = new FormData();
formData.append(
	'instructions',
	JSON.stringify({
		parts: [
			{
				file: 'first_half',
			},
			{
				file: 'second_half',
			},
		],
	}),
);
formData.append('first_half', fs.createReadStream('first_half.pdf'));
formData.append('second_half', fs.createReadStream('second_half.pdf'));

(async () => {
	try {
		const response = await axios.post(
			'https://api.pspdfkit.com/build',
			formData,
			{
				headers: formData.getHeaders({
					Authorization: 'Bearer YOUR_API_KEY', // Replace `YOUR_API_KEY` with your API key.
				}),
				responseType: 'stream',
			},
		);

		response.data.pipe(fs.createWriteStream('result.pdf'));
	} catch (e) {
		const errorString = await streamToString(e.response.data);
		console.log(errorString);
	}
})();

function streamToString(stream) {
	const chunks = [];
	return new Promise((resolve, reject) => {
		stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
		stream.on('error', (err) => reject(err));
		stream.on('end', () =>
			resolve(Buffer.concat(chunks).toString('utf8')),
		);
	});
}
Information

Replace YOUR_API_KEY with your API key.

Add your PDF files to the merge_pdf folder. In this example, you’re using first_half.pdf and second_half.pdf as your input files.

This code snippet demonstrates the process of merging two PDF files into a single PDF document. It utilizes the axios library for making the API request, FormData for handling form data, and fs for file system operations. The code creates a new FormData instance, appends the merging instructions and the PDF files to be merged, and then performs an API call to PSPDFKit API. The resulting merged PDF document is streamed and saved as result.pdf.

Splitting PDFs Using PSPDFKit API

In this example, explore the process of splitting PDF files using the Split PDF API.

  1. Create a new folder named split_pdf and open it in a code editor.

  2. Inside the split_pdf folder, create a new file named processor.js. Copy the following code and paste it into the processor.js file:

// processor.js
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

(async () => {
	const firstHalf = new FormData();
	firstHalf.append(
		'instructions',
		JSON.stringify({
			parts: [
				{
					file: 'document',
					pages: {
						end: -2,
					},
				},
			],
		}),
	);
	firstHalf.append('document', fs.createReadStream('input.pdf')); // Add your document here.

	const secondHalf = new FormData();
	secondHalf.append(
		'instructions',
		JSON.stringify({
			parts: [
				{
					file: 'document',
					pages: {
						start: -1,
					},
				},
			],
		}),
	);
	secondHalf.append('document', fs.createReadStream('input.pdf'));

	await executeRequest(firstHalf, 'first_half.pdf');
	await executeRequest(secondHalf, 'second_half.pdf');
})();

async function executeRequest(formData, outputFile) {
	try {
		const response = await axios.post(
			'https://api.pspdfkit.com/build',
			formData,
			{
				headers: formData.getHeaders({
					Authorization: 'Bearer YOUR_API_KEY_HERE', // Replace with your API key.
				}),
				responseType: 'stream',
			},
		);

		response.data.pipe(fs.createWriteStream(outputFile));
	} catch (e) {
		const errorString = await streamToString(e.response.data);
		console.log(errorString);
	}
}

function streamToString(stream) {
	const chunks = [];
	return new Promise((resolve, reject) => {
		stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
		stream.on('error', (err) => reject(err));
		stream.on('end', () =>
			resolve(Buffer.concat(chunks).toString('utf8')),
		);
	});
}
Information

Replace YOUR_API_KEY_HERE with your API key.

Add the PDF file you want to split and name it input.pdf. Ensure the file is in the same folder as the processor.js file.

The code snippet uses an immediately invoked async function expression (IIFE) to execute the PDF splitting process. It leverages the FormData object to configure splitting instructions and attach the PDF file. With specified page ranges, you can precisely define the parts you need.

The executeRequest function handles the API request to PSPDFKit API, ensuring a secure and reliable connection. It streams the response containing the split PDF content and saves it to separate output files using fs.createWriteStream.

Deleting Pages Using PSPDFKit API

In this example, learn how to delete one or more pages from a PDF file using the Delete PDF Page API.

  1. Create a new folder named delete_pages and open it in a code editor.

  2. Add the PDF file you want to delete pages from and name it input.pdf.

  3. Create a new file named processor.js and copy the following code into it:

const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

const formData = new FormData();
formData.append(
	'instructions',
	JSON.stringify({
		parts: [
			{
				file: 'document',
				pages: {
					end: 2,
				},
			},
			{
				file: 'document',
				pages: {
					start: 4,
				},
			},
		],
	}),
);
formData.append('document', fs.createReadStream('input.pdf')); // Add your document here.

(async () => {
	try {
		const response = await axios.post(
			'https://api.pspdfkit.com/build',
			formData,
			{
				headers: formData.getHeaders({
					Authorization: 'Bearer YOUR_API_KEY_HERE', // Replace with your API key.
				}),
				responseType: 'stream',
			},
		);

		response.data.pipe(fs.createWriteStream('result.pdf'));
	} catch (e) {
		const errorString = await streamToString(e.response.data);
		console.log(errorString);
	}
})();

function streamToString(stream) {
	const chunks = [];
	return new Promise((resolve, reject) => {
		stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
		stream.on('error', (err) => reject(err));
		stream.on('end', () =>
			resolve(Buffer.concat(chunks).toString('utf8')),
		);
	});
}
Information

Replace YOUR_API_KEY_HERE with your API key.

After importing the necessary packages, a FormData object was created to hold the instructions for the API processing. In this case, the code demonstrates the removal of page number 4, but you can modify it to target a different page. The input document was read using the fs.createReadStream function. The API call was made using axios, and the resulting response was stored in a file named result.pdf.

Flattening PDFs Using PSPDFKit API

Learn how to programmatically flatten PDFs using the Flatten PDF API. Streamline your workflow by automating the flattening process, ensuring accurate printing and compatibility with PDF viewers, and protecting form field data.

In this example, you’ll flatten all the annotations present in your provided document, effectively rendering them non-editable. This process guarantees that the annotations become a permanent part of the PDF and can no longer be modified.

  1. Create a new folder named flatten and open it in a code editor.

  2. Add the PDF file you want to flatten and name it input.pdf.

  3. Create a new file named processor.js and copy the following code into it:

const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

const formData = new FormData();
formData.append(
	'instructions',
	JSON.stringify({
		parts: [
			{
				file: 'document',
			},
		],
		actions: [
			{
				type: 'flatten',
			},
		],
	}),
);
formData.append('document', fs.createReadStream('input.pdf')); // Add your document here.

(async () => {
	try {
		const response = await axios.post(
			'https://api.pspdfkit.com/build',
			formData,
			{
				headers: formData.getHeaders({
					Authorization: 'Bearer YOUR_API_KEY_HERE', // Replace with your API key.
				}),
				responseType: 'stream',
			},
		);

		response.data.pipe(fs.createWriteStream('result.pdf'));
	} catch (e) {
		const errorString = await streamToString(e.response.data);
		console.log(errorString);
	}
})();

function streamToString(stream) {
	const chunks = [];
	return new Promise((resolve, reject) => {
		stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
		stream.on('error', (err) => reject(err));
		stream.on('end', () =>
			resolve(Buffer.concat(chunks).toString('utf8')),
		);
	});
}
Information

Replace YOUR_API_KEY_HERE with your API key.

This code snippet creates a FormData object with instructions to flatten the specified document. The API request is made using axios.post(), and the flattened PDF response is saved as result.pdf using a writable stream.

Duplicating Pages Using PSPDFKit API

In this example, you’ll explore how to duplicate specific PDF pages using the Duplicate PDF Page API provided by PSPDFKit. By duplicating PDF pages using the Duplicate PDF Page API, you can perform operations such as watermarking, merging, and preserving backups, allowing for enhanced document workflows with ease and automation.

  1. Create a new folder named duplicate_pages and open it in a code editor.

  2. Add a PDF file you want to duplicate pages from and name it input.pdf.

  3. Create a new file named processor.js and copy the following code into it:

const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

const formData = new FormData();
formData.append(
	'instructions',
	JSON.stringify({
		parts: [
			{
				file: 'document',
				pages: {
					start: 0,
					end: 0,
				},
			},
			{
				file: 'document',
			},
			{
				file: 'document',
				pages: {
					start: -1,
					end: -1,
				},
			},
		],
	}),
);
formData.append('document', fs.createReadStream('input.pdf'));
(async () => {
	try {
		const response = await axios.post(
			'https://api.pspdfkit.com/build',
			formData,
			{
				headers: formData.getHeaders({
					Authorization: 'Bearer YOUR_API_KEY_HERE', // Replace with your API key.
				}),
				responseType: 'stream',
			},
		);

		response.data.pipe(fs.createWriteStream('result.pdf'));
	} catch (e) {
		const errorString = await streamToString(e.response.data);
		console.log(errorString);
	}
})();

function streamToString(stream) {
	const chunks = [];
	return new Promise((resolve, reject) => {
		stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
		stream.on('error', (err) => reject(err));
		stream.on('end', () =>
			resolve(Buffer.concat(chunks).toString('utf8')),
		);
	});
}
Information

Replace YOUR_API_KEY_HERE with your API key.

In this code example, the first and last pages of a document are duplicated. By providing the specific pages to be duplicated, the code makes a request to PSPDFKit API. The API returns the duplicated PDF as a response, which is then saved as result.pdf for further use.

Conclusion

In this tutorial, you explored the capabilities of the pdf-lib library and learned how to build a PDF editor in Node.js. You covered various tasks, such as reading and writing PDF files, adding text annotations, combining pages from different PDFs, and embedding images.

In the second part of the tutorial, you learned about PSPDFKit API as an alternative solution for PDF editing. Leveraging the power of PSPDFKit API, you can efficiently manipulate PDF documents, streamline workflows, and enhance document management processes.

By comparing PSPDFKit API and pdf-lib, this post summarized the key points of each solution to help you make an informed decision. PSPDFKit API offers advanced features, SOC 2-compliant security, easy integration, versatile actions, and transparent pricing. Meanwhile, pdf-lib is suitable for basic editing needs, compatible with various JavaScript environments, and an open source solution.

To get started with PSPDFKit API, create a free account and obtain your API key. You can also check out the PSPDFKit API documentation for more information.

Author
Hulya Karakaya Technical Writer

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

Related Products
Share Post
Free 60-Day Trial Try PSPDFKit in your app today.
Free Trial

Related Articles

Explore more
DESIGN  |  Baseline UI • Web

Part V — Mastering the Baseline UI Theme: An In-Depth Exploration

DESIGN  |  Baseline UI • Web

Part IV — Building Consistency: A Guide to Design Tokens in Baseline UI

DESIGN  |  Baseline UI • Web

Part III — Accessible UI Design: Building Inclusive Digital Experiences