No articles found
Try different keywords or browse our categories
How to Use pdf-lib in HTML: Create & Edit PDFs in Browser (Complete Guide)
Learn to create and edit PDFs directly in the browser with pdf-lib. Step-by-step guide with working examples for forms, watermarks, and PDF manipulation.
pdf-lib is a powerful JavaScript library that lets you create and modify PDFs entirely in the browser. Unlike other PDF libraries, pdf-lib can edit existing PDFs, fill forms, add watermarks, merge documents, and more.
What You’ll Build
By the end of this tutorial, you’ll have a working application that can:
- Create PDFs from scratch with custom fonts and styling
- Edit existing PDFs by adding text and watermarks
- Fill PDF form fields programmatically
- Merge multiple PDF documents
- Add images and custom graphics to PDFs
All operations run entirely in the browser with no server required.
Why Choose pdf-lib?
pdf-lib stands out because it can:
- Edit Existing PDFs: Modify PDFs without recreating them from scratch
- Fill Forms: Populate PDF form fields automatically
- Merge Documents: Combine multiple PDFs into one
- Advanced Features: Add images, embed fonts, rotate pages, and more
- No Dependencies: Pure JavaScript with no external requirements
Here’s what the complete interface looks like:
A clean, modern interface for PDF manipulation
Setting Up pdf-lib
Add the pdf-lib library via CDN. This loads the library and makes it available globally:
<script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
Once loaded, pdf-lib is available as window.PDFLib.
Building the HTML Structure
Create a clean interface with sections for each PDF operation. We’ll include file upload inputs for PDF editing:
<div class="container">
<h1>pdf-lib Working Examples</h1>
<p class="subtitle">Create and edit PDFs directly in your browser</p>
<div class="section">
<h2>Example 1: Create PDF from Scratch</h2>
<p class="description">Generate a new PDF with text, colors, and multiple pages</p>
<button onclick="createPDF()">Create & Download PDF</button>
</div>
<div class="section">
<h2>Example 2: Edit Existing PDF</h2>
<p class="description">Upload a PDF and add custom text overlay</p>
<input type="file" id="pdfFile" accept=".pdf">
<button onclick="editPDF()">Add Text & Download</button>
</div>
<div class="section">
<h2>Example 3: Add Watermark to PDF</h2>
<p class="description">Upload a PDF and add a watermark to all pages</p>
<input type="file" id="watermarkFile" accept=".pdf">
<input type="text" id="watermarkText" placeholder="Enter watermark text">
<button onclick="addWatermark()">Add Watermark & Download</button>
</div>
<div class="section">
<h2>Example 4: Fill PDF Form</h2>
<p class="description">Create a form PDF and fill it with data</p>
<form id="formData" onsubmit="fillForm(event)">
<div class="form-grid">
<input type="text" name="fullName" placeholder="Full Name" required>
<input type="email" name="email" placeholder="Email Address" required>
<input type="text" name="company" placeholder="Company Name" required>
<input type="text" name="position" placeholder="Position" required>
</div>
<button type="submit">Generate Filled Form</button>
</form>
</div>
<div class="section">
<h2>Example 5: Merge PDFs</h2>
<p class="description">Upload multiple PDFs to merge into one</p>
<input type="file" id="mergeFiles" accept=".pdf" multiple>
<button onclick="mergePDFs()">Merge & Download</button>
</div>
</div>
The structure includes file inputs for uploading PDFs, text inputs for customization, and buttons to trigger operations. Each section clearly describes what it does.
Example of a well-organized form layout:
Clean form design improves user experience
Styling with CSS
Professional styling creates a clean, modern interface:
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 40px 20px;
background: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
margin-bottom: 10px;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 16px;
}
.section {
margin-bottom: 40px;
padding: 25px;
background: #f9f9f9;
border-radius: 8px;
border-left: 4px solid #10b981;
}
h2 {
color: #10b981;
margin-top: 0;
margin-bottom: 8px;
}
.description {
color: #666;
margin-bottom: 15px;
font-size: 14px;
}
button {
background: #10b981;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
margin: 8px 8px 8px 0;
transition: background 0.2s;
}
button:hover {
background: #059669;
}
input[type="file"],
input[type="text"],
input[type="email"] {
width: 100%;
padding: 10px;
margin: 8px 0;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
}
@media (max-width: 600px) {
.form-grid {
grid-template-columns: 1fr;
}
}
The CSS uses a green color scheme to differentiate from the jsPDF tutorial, with responsive layouts and hover effects for better user experience.
Visual design matters:
Professional styling creates trust and improves usability
JavaScript: PDF Creation and Editing
Example 1: Create PDF from Scratch
This example shows how to create a complete PDF with text, colors, and multiple pages:
async function createPDF() {
const { PDFDocument, rgb, StandardFonts } = PDFLib;
const pdfDoc = await PDFDocument.create();
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman);
const helveticaBoldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const page = pdfDoc.addPage([600, 750]);
const { width, height } = page.getSize();
page.drawRectangle({
x: 0,
y: height - 100,
width: width,
height: 100,
color: rgb(0.2, 0.6, 0.8),
});
page.drawText('Professional Document', {
x: 50,
y: height - 60,
size: 32,
font: helveticaBoldFont,
color: rgb(1, 1, 1),
});
page.drawText('Created with pdf-lib', {
x: 50,
y: height - 85,
size: 14,
font: timesRomanFont,
color: rgb(0.9, 0.9, 0.9),
});
page.drawText('This PDF was generated entirely in your browser using pdf-lib.', {
x: 50,
y: height - 150,
size: 14,
font: timesRomanFont,
color: rgb(0, 0, 0),
});
page.drawText('Key features demonstrated:', {
x: 50,
y: height - 180,
size: 14,
font: helveticaBoldFont,
color: rgb(0, 0, 0),
});
const features = [
'• Custom fonts (Times Roman, Helvetica Bold)',
'• Colored shapes and backgrounds',
'• Precise text positioning',
'• Multiple pages with content',
'• RGB color control'
];
let yPos = height - 210;
features.forEach(feature => {
page.drawText(feature, {
x: 70,
y: yPos,
size: 12,
font: timesRomanFont,
color: rgb(0.2, 0.2, 0.2),
});
yPos -= 25;
});
page.drawRectangle({
x: 50,
y: yPos - 20,
width: 500,
height: 60,
color: rgb(0.95, 0.95, 0.95),
borderColor: rgb(0.7, 0.7, 0.7),
borderWidth: 1,
});
page.drawText('PDF creation is powerful with pdf-lib!', {
x: 60,
y: yPos,
size: 13,
font: helveticaBoldFont,
color: rgb(0.2, 0.6, 0.8),
});
const secondPage = pdfDoc.addPage([600, 750]);
secondPage.drawText('Page 2', {
x: 50,
y: 700,
size: 24,
font: helveticaBoldFont,
color: rgb(0, 0, 0),
});
secondPage.drawText('pdf-lib supports multiple pages seamlessly.', {
x: 50,
y: 660,
size: 12,
font: timesRomanFont,
color: rgb(0, 0, 0),
});
const pdfBytes = await pdfDoc.save();
downloadPDF(pdfBytes, 'created-document.pdf');
}
Key features:
PDFDocument.create()creates a new blank PDFembedFont()loads standard fonts for useaddPage([width, height])creates pages with custom dimensionsdrawRectangle()anddrawText()add shapes and textrgb()function defines colors with values from 0 to 1
Example 2: Edit Existing PDF
Upload a PDF and add custom text to it:
async function editPDF() {
const fileInput = document.getElementById('pdfFile');
if (!fileInput.files.length) {
alert('Please select a PDF file first');
return;
}
const { PDFDocument, rgb, StandardFonts } = PDFLib;
const file = fileInput.files[0];
const arrayBuffer = await file.arrayBuffer();
const pdfDoc = await PDFDocument.load(arrayBuffer);
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
const pages = pdfDoc.getPages();
const firstPage = pages[0];
const { width, height } = firstPage.getSize();
firstPage.drawText('EDITED WITH PDF-LIB', {
x: width / 2 - 100,
y: height - 50,
size: 20,
font: helveticaFont,
color: rgb(0.95, 0.1, 0.1),
});
firstPage.drawText(`Edited on: ${new Date().toLocaleDateString()}`, {
x: 50,
y: 30,
size: 10,
font: helveticaFont,
color: rgb(0.5, 0.5, 0.5),
});
const pdfBytes = await pdfDoc.save();
downloadPDF(pdfBytes, 'edited-document.pdf');
}
This function:
- Uses
FileReaderto load the uploaded PDF PDFDocument.load()loads the existing PDF for editinggetPages()retrieves all pages for manipulation- Adds new text on top of existing content
- Preserves all original content
Example 3: Add Watermark to PDF
Add a diagonal watermark across all pages:
async function addWatermark() {
const fileInput = document.getElementById('watermarkFile');
const watermarkInput = document.getElementById('watermarkText');
if (!fileInput.files.length) {
alert('Please select a PDF file');
return;
}
if (!watermarkInput.value.trim()) {
alert('Please enter watermark text');
return;
}
const { PDFDocument, rgb, StandardFonts, degrees } = PDFLib;
const file = fileInput.files[0];
const arrayBuffer = await file.arrayBuffer();
const pdfDoc = await PDFDocument.load(arrayBuffer);
const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const pages = pdfDoc.getPages();
pages.forEach(page => {
const { width, height } = page.getSize();
const watermarkText = watermarkInput.value.toUpperCase();
const textWidth = font.widthOfTextAtSize(watermarkText, 60);
page.drawText(watermarkText, {
x: width / 2 - textWidth / 2,
y: height / 2,
size: 60,
font: font,
color: rgb(0.8, 0.8, 0.8),
opacity: 0.3,
rotate: degrees(-45),
});
});
const pdfBytes = await pdfDoc.save();
downloadPDF(pdfBytes, 'watermarked-document.pdf');
}
Features demonstrated:
- Loops through all pages with
forEach() - Centers text using font width calculations
rotate: degrees(-45)rotates text diagonallyopacity: 0.3makes the watermark semi-transparent- Same watermark appears on every page
Example 4: Fill PDF Form
Create a form PDF and populate it with user data:
async function fillForm(event) {
event.preventDefault();
const formData = new FormData(event.target);
const data = Object.fromEntries(formData.entries());
const { PDFDocument, rgb, StandardFonts } = PDFLib;
const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const page = pdfDoc.addPage([600, 750]);
const { width, height } = page.getSize();
page.drawRectangle({
x: 0,
y: height - 80,
width: width,
height: 80,
color: rgb(0.1, 0.4, 0.7),
});
page.drawText('REGISTRATION FORM', {
x: width / 2 - 120,
y: height - 50,
size: 28,
font: boldFont,
color: rgb(1, 1, 1),
});
let yPosition = height - 140;
page.drawRectangle({
x: 50,
y: yPosition - 25,
width: 500,
height: 40,
color: rgb(0.96, 0.96, 0.96),
});
page.drawText('Full Name:', {
x: 60,
y: yPosition,
size: 12,
font: boldFont,
color: rgb(0.2, 0.2, 0.2),
});
page.drawText(data.fullName, {
x: 200,
y: yPosition,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
yPosition -= 60;
page.drawRectangle({
x: 50,
y: yPosition - 25,
width: 500,
height: 40,
color: rgb(0.96, 0.96, 0.96),
});
page.drawText('Email Address:', {
x: 60,
y: yPosition,
size: 12,
font: boldFont,
color: rgb(0.2, 0.2, 0.2),
});
page.drawText(data.email, {
x: 200,
y: yPosition,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
yPosition -= 60;
page.drawRectangle({
x: 50,
y: yPosition - 25,
width: 500,
height: 40,
color: rgb(0.96, 0.96, 0.96),
});
page.drawText('Company:', {
x: 60,
y: yPosition,
size: 12,
font: boldFont,
color: rgb(0.2, 0.2, 0.2),
});
page.drawText(data.company, {
x: 200,
y: yPosition,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
yPosition -= 60;
page.drawRectangle({
x: 50,
y: yPosition - 25,
width: 500,
height: 40,
color: rgb(0.96, 0.96, 0.96),
});
page.drawText('Position:', {
x: 60,
y: yPosition,
size: 12,
font: boldFont,
color: rgb(0.2, 0.2, 0.2),
});
page.drawText(data.position, {
x: 200,
y: yPosition,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
yPosition -= 80;
page.drawText('Form generated on: ' + new Date().toLocaleString(), {
x: 60,
y: yPosition,
size: 10,
font: font,
color: rgb(0.5, 0.5, 0.5),
});
page.drawText('Signature: _______________________', {
x: 60,
y: 100,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
page.drawText('Date: _______________________', {
x: 350,
y: 100,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
const pdfBytes = await pdfDoc.save();
downloadPDF(pdfBytes, `registration-${data.fullName.replace(/\s+/g, '-')}.pdf`);
event.target.reset();
alert('Form PDF generated successfully!');
}
This demonstrates:
- Extracting form data using FormData API
- Creating a structured form layout
- Alternating background colors for rows
- Dynamic filename based on user input
- Form reset after successful generation
Example 5: Merge Multiple PDFs
Combine multiple uploaded PDFs into one document:
async function mergePDFs() {
const fileInput = document.getElementById('mergeFiles');
if (!fileInput.files.length) {
alert('Please select at least 2 PDF files to merge');
return;
}
if (fileInput.files.length < 2) {
alert('Please select at least 2 PDF files');
return;
}
const { PDFDocument } = PDFLib;
const mergedPdf = await PDFDocument.create();
for (let i = 0; i < fileInput.files.length; i++) {
const file = fileInput.files[i];
const arrayBuffer = await file.arrayBuffer();
const pdf = await PDFDocument.load(arrayBuffer);
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
copiedPages.forEach(page => {
mergedPdf.addPage(page);
});
}
const mergedPdfBytes = await mergedPdf.save();
downloadPDF(mergedPdfBytes, 'merged-document.pdf');
alert(`Successfully merged ${fileInput.files.length} PDF files!`);
}
Key concepts:
PDFDocument.create()creates the target merged PDF- Loop through all selected files
copyPages()copies pages from source to destinationgetPageIndices()gets all page indices from a document- All pages maintain their original formatting
Helper Function: Download PDF
This utility function handles the PDF download:
function downloadPDF(pdfBytes, filename) {
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}
This function:
- Creates a Blob from PDF bytes
- Generates a temporary URL
- Triggers automatic download
- Cleans up the URL to free memory
Complete Working Example
Here’s the complete HTML file with all code together. Copy and save as pdf-lib-demo.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>pdf-lib Examples - Create & Edit PDFs</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 40px 20px;
background: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
margin-bottom: 10px;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 16px;
}
.section {
margin-bottom: 40px;
padding: 25px;
background: #f9f9f9;
border-radius: 8px;
border-left: 4px solid #10b981;
}
h2 {
color: #10b981;
margin-top: 0;
margin-bottom: 8px;
}
.description {
color: #666;
margin-bottom: 15px;
font-size: 14px;
}
button {
background: #10b981;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
margin: 8px 8px 8px 0;
transition: background 0.2s;
}
button:hover {
background: #059669;
}
input[type="file"],
input[type="text"],
input[type="email"] {
width: 100%;
padding: 10px;
margin: 8px 0;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
}
@media (max-width: 600px) {
.form-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<h1>pdf-lib Working Examples</h1>
<p class="subtitle">Create and edit PDFs directly in your browser</p>
<div class="section">
<h2>Example 1: Create PDF from Scratch</h2>
<p class="description">Generate a new PDF with text, colors, and multiple pages</p>
<button onclick="createPDF()">Create & Download PDF</button>
</div>
<div class="section">
<h2>Example 2: Edit Existing PDF</h2>
<p class="description">Upload a PDF and add custom text overlay</p>
<input type="file" id="pdfFile" accept=".pdf">
<button onclick="editPDF()">Add Text & Download</button>
</div>
<div class="section">
<h2>Example 3: Add Watermark to PDF</h2>
<p class="description">Upload a PDF and add a watermark to all pages</p>
<input type="file" id="watermarkFile" accept=".pdf">
<input type="text" id="watermarkText" placeholder="Enter watermark text">
<button onclick="addWatermark()">Add Watermark & Download</button>
</div>
<div class="section">
<h2>Example 4: Fill PDF Form</h2>
<p class="description">Create a form PDF and fill it with data</p>
<form id="formData" onsubmit="fillForm(event)">
<div class="form-grid">
<input type="text" name="fullName" placeholder="Full Name" required>
<input type="email" name="email" placeholder="Email Address" required>
<input type="text" name="company" placeholder="Company Name" required>
<input type="text" name="position" placeholder="Position" required>
</div>
<button type="submit">Generate Filled Form</button>
</form>
</div>
<div class="section">
<h2>Example 5: Merge PDFs</h2>
<p class="description">Upload multiple PDFs to merge into one</p>
<input type="file" id="mergeFiles" accept=".pdf" multiple>
<button onclick="mergePDFs()">Merge & Download</button>
</div>
</div>
<script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
<script>
function downloadPDF(pdfBytes, filename) {
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}
async function createPDF() {
const { PDFDocument, rgb, StandardFonts } = PDFLib;
const pdfDoc = await PDFDocument.create();
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman);
const helveticaBoldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const page = pdfDoc.addPage([600, 750]);
const { width, height } = page.getSize();
page.drawRectangle({
x: 0,
y: height - 100,
width: width,
height: 100,
color: rgb(0.2, 0.6, 0.8),
});
page.drawText('Professional Document', {
x: 50,
y: height - 60,
size: 32,
font: helveticaBoldFont,
color: rgb(1, 1, 1),
});
page.drawText('Created with pdf-lib', {
x: 50,
y: height - 85,
size: 14,
font: timesRomanFont,
color: rgb(0.9, 0.9, 0.9),
});
page.drawText('This PDF was generated entirely in your browser using pdf-lib.', {
x: 50,
y: height - 150,
size: 14,
font: timesRomanFont,
color: rgb(0, 0, 0),
});
page.drawText('Key features demonstrated:', {
x: 50,
y: height - 180,
size: 14,
font: helveticaBoldFont,
color: rgb(0, 0, 0),
});
const features = [
'• Custom fonts (Times Roman, Helvetica Bold)',
'• Colored shapes and backgrounds',
'• Precise text positioning',
'• Multiple pages with content',
'• RGB color control'
];
let yPos = height - 210;
features.forEach(feature => {
page.drawText(feature, {
x: 70,
y: yPos,
size: 12,
font: timesRomanFont,
color: rgb(0.2, 0.2, 0.2),
});
yPos -= 25;
});
page.drawRectangle({
x: 50,
y: yPos - 20,
width: 500,
height: 60,
color: rgb(0.95, 0.95, 0.95),
borderColor: rgb(0.7, 0.7, 0.7),
borderWidth: 1,
});
page.drawText('PDF creation is powerful with pdf-lib!', {
x: 60,
y: yPos,
size: 13,
font: helveticaBoldFont,
color: rgb(0.2, 0.6, 0.8),
});
const secondPage = pdfDoc.addPage([600, 750]);
secondPage.drawText('Page 2', {
x: 50,
y: 700,
size: 24,
font: helveticaBoldFont,
color: rgb(0, 0, 0),
});
secondPage.drawText('pdf-lib supports multiple pages seamlessly.', {
x: 50,
y: 660,
size: 12,
font: timesRomanFont,
color: rgb(0, 0, 0),
});
const pdfBytes = await pdfDoc.save();
downloadPDF(pdfBytes, 'created-document.pdf');
}
async function editPDF() {
const fileInput = document.getElementById('pdfFile');
if (!fileInput.files.length) {
alert('Please select a PDF file first');
return;
}
const { PDFDocument, rgb, StandardFonts } = PDFLib;
const file = fileInput.files[0];
const arrayBuffer = await file.arrayBuffer();
const pdfDoc = await PDFDocument.load(arrayBuffer);
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
const pages = pdfDoc.getPages();
const firstPage = pages[0];
const { width, height } = firstPage.getSize();
firstPage.drawText('EDITED WITH PDF-LIB', {
x: width / 2 - 100,
y: height - 50,
size: 20,
font: helveticaFont,
color: rgb(0.95, 0.1, 0.1),
});
firstPage.drawText(`Edited on: ${new Date().toLocaleDateString()}`, {
x: 50,
y: 30,
size: 10,
font: helveticaFont,
color: rgb(0.5, 0.5, 0.5),
});
const pdfBytes = await pdfDoc.save();
downloadPDF(pdfBytes, 'edited-document.pdf');
}
async function addWatermark() {
const fileInput = document.getElementById('watermarkFile');
const watermarkInput = document.getElementById('watermarkText');
if (!fileInput.files.length) {
alert('Please select a PDF file');
return;
}
if (!watermarkInput.value.trim()) {
alert('Please enter watermark text');
return;
}
const { PDFDocument, rgb, StandardFonts, degrees } = PDFLib;
const file = fileInput.files[0];
const arrayBuffer = await file.arrayBuffer();
const pdfDoc = await PDFDocument.load(arrayBuffer);
const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const pages = pdfDoc.getPages();
pages.forEach(page => {
const { width, height } = page.getSize();
const watermarkText = watermarkInput.value.toUpperCase();
const textWidth = font.widthOfTextAtSize(watermarkText, 60);
page.drawText(watermarkText, {
x: width / 2 - textWidth / 2,
y: height / 2,
size: 60,
font: font,
color: rgb(0.8, 0.8, 0.8),
opacity: 0.3,
rotate: degrees(-45),
});
});
const pdfBytes = await pdfDoc.save();
downloadPDF(pdfBytes, 'watermarked-document.pdf');
}
async function fillForm(event) {
event.preventDefault();
const formData = new FormData(event.target);
const data = Object.fromEntries(formData.entries());
const { PDFDocument, rgb, StandardFonts } = PDFLib;
const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const page = pdfDoc.addPage([600, 750]);
const { width, height } = page.getSize();
page.drawRectangle({
x: 0,
y: height - 80,
width: width,
height: 80,
color: rgb(0.1, 0.4, 0.7),
});
page.drawText('REGISTRATION FORM', {
x: width / 2 - 120,
y: height - 50,
size: 28,
font: boldFont,
color: rgb(1, 1, 1),
});
let yPosition = height - 140;
page.drawRectangle({
x: 50,
y: yPosition - 25,
width: 500,
height: 40,
color: rgb(0.96, 0.96, 0.96),
});
page.drawText('Full Name:', {
x: 60,
y: yPosition,
size: 12,
font: boldFont,
color: rgb(0.2, 0.2, 0.2),
});
page.drawText(data.fullName, {
x: 200,
y: yPosition,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
yPosition -= 60;
page.drawRectangle({
x: 50,
y: yPosition - 25,
width: 500,
height: 40,
color: rgb(0.96, 0.96, 0.96),
});
page.drawText('Email Address:', {
x: 60,
y: yPosition,
size: 12,
font: boldFont,
color: rgb(0.2, 0.2, 0.2),
});
page.drawText(data.email, {
x: 200,
y: yPosition,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
yPosition -= 60;
page.drawRectangle({
x: 50,
y: yPosition - 25,
width: 500,
height: 40,
color: rgb(0.96, 0.96, 0.96),
});
page.drawText('Company:', {
x: 60,
y: yPosition,
size: 12,
font: boldFont,
color: rgb(0.2, 0.2, 0.2),
});
page.drawText(data.company, {
x: 200,
y: yPosition,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
yPosition -= 60;
page.drawRectangle({
x: 50,
y: yPosition - 25,
width: 500,
height: 40,
color: rgb(0.96, 0.96, 0.96),
});
page.drawText('Position:', {
x: 60,
y: yPosition,
size: 12,
font: boldFont,
color: rgb(0.2, 0.2, 0.2),
});
page.drawText(data.position, {
x: 200,
y: yPosition,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
yPosition -= 80;
page.drawText('Form generated on: ' + new Date().toLocaleString(), {
x: 60,
y: yPosition,
size: 10,
font: font,
color: rgb(0.5, 0.5, 0.5),
});
page.drawText('Signature: _______________________', {
x: 60,
y: 100,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
page.drawText('Date: _______________________', {
x: 350,
y: 100,
size: 12,
font: font,
color: rgb(0, 0, 0),
});
const pdfBytes = await pdfDoc.save();
downloadPDF(pdfBytes, `registration-${data.fullName.replace(/\s+/g, '-')}.pdf`);
event.target.reset();
alert('Form PDF generated successfully!');
}
async function mergePDFs() {
const fileInput = document.getElementById('mergeFiles');
if (!fileInput.files.length) {
alert('Please select at least 2 PDF files to merge');
return;
}
if (fileInput.files.length < 2) {
alert('Please select at least 2 PDF files');
return;
}
const { PDFDocument } = PDFLib;
const mergedPdf = await PDFDocument.create();
for (let i = 0; i < fileInput.files.length; i++) {
const file = fileInput.files[i];
const arrayBuffer = await file.arrayBuffer();
const pdf = await PDFDocument.load(arrayBuffer);
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
copiedPages.forEach(page => {
mergedPdf.addPage(page);
});
}
const mergedPdfBytes = await mergedPdf.save();
downloadPDF(mergedPdfBytes, 'merged-document.pdf');
alert(`Successfully merged ${fileInput.files.length} PDF files!`);
}
</script>
</body>
</html>
Copy the complete HTML above and save it as pdf-lib-demo.html. Open it in any browser to test all examples.
Result visualization:
Successfully generated PDFs ready for download
Key Concepts Explained
Async/Await Pattern
pdf-lib uses promises extensively. Always use async functions and await for PDF operations:
const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const pdfBytes = await pdfDoc.save();
RGB Color System
Colors use values from 0 to 1 (not 0-255):
rgb(1, 0, 0)is redrgb(0, 1, 0)is greenrgb(0.5, 0.5, 0.5)is gray
Coordinate System
pdf-lib uses points with origin at bottom-left:
- X increases to the right
- Y increases upward
- Use
height - yto position from top
Standard Fonts
Available fonts without additional files:
TimesRoman,TimesRomanBold,TimesRomanItalicHelvetica,HelveticaBold,HelveticaObliqueCourier,CourierBold,CourierOblique
Browser Compatibility
pdf-lib works in all modern browsers:
- Chrome 60+
- Firefox 55+
- Safari 11+
- Edge 79+
Requires ES6 support (async/await, Promises).
Performance Considerations
For optimal performance:
- Large Files: Use web workers for processing large PDFs
- Memory: Call
URL.revokeObjectURL()to free memory after downloads - Multiple Operations: Process PDFs one at a time, not simultaneously
- Loading States: Show progress indicators for slow operations
Common Use Cases
This tutorial covers essential scenarios:
Document Management: Edit contracts, add signatures, fill forms Branding: Add watermarks and company logos to documents Automation: Generate certificates, badges, and reports Workflow: Merge multiple documents into consolidated files Customization: Create personalized PDFs based on user input
Advantages Over Other Libraries
pdf-lib excels at:
Editing Existing PDFs: Unlike jsPDF which only creates new PDFs Form Handling: Native support for PDF form fields Page Manipulation: Copy, move, delete, and rotate pages No Dependencies: Pure JavaScript with no external requirements Type Safety: Full TypeScript support with type definitions
Next Steps
Build on these examples by:
- Adding images with
embedPng()orembedJpg() - Working with PDF forms using
getForm() - Embedding custom fonts with
embedFont() - Encrypting PDFs with password protection
- Extracting text and metadata from existing PDFs
The complete examples above provide a solid foundation for any PDF manipulation task in your web applications.
Related Articles
jsPDF Tutorial: Generate PDF in Browser Using HTML & JavaScript (Full Working Example)
Learn to create PDFs directly in the browser with jsPDF. Step-by-step guide with working examples for invoices, tickets, and styled documents.
Build PDFs Directly in the Browser: jsPDF vs pdf-lib vs PDF.js (Real Examples & Use Cases)
A practical comparison of jsPDF, pdf-lib, and PDF.js for browser-based PDF generation and manipulation. Learn which library fits your project with real code examples.
How to Integrate Mozilla PDF.js in HTML: Build a PDF Viewer in Browser
Quick guide to integrating Mozilla PDF.js into your HTML application to build a functional PDF viewer directly in the browser.