search
HTML star Featured

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.

person By Gautam Sharma
calendar_today December 31, 2024
schedule 19 min read
HTML JavaScript PDF pdf-lib Frontend

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:

pdf-lib Interface Example 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:

Form Layout Example 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:

Modern UI Design 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 PDF
  • embedFont() loads standard fonts for use
  • addPage([width, height]) creates pages with custom dimensions
  • drawRectangle() and drawText() add shapes and text
  • rgb() 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 FileReader to load the uploaded PDF
  • PDFDocument.load() loads the existing PDF for editing
  • getPages() 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 diagonally
  • opacity: 0.3 makes 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 destination
  • getPageIndices() 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:

PDF Generation Success 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 red
  • rgb(0, 1, 0) is green
  • rgb(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 - y to position from top

Standard Fonts

Available fonts without additional files:

  • TimesRoman, TimesRomanBold, TimesRomanItalic
  • Helvetica, HelveticaBold, HelveticaOblique
  • Courier, 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:

  1. Large Files: Use web workers for processing large PDFs
  2. Memory: Call URL.revokeObjectURL() to free memory after downloads
  3. Multiple Operations: Process PDFs one at a time, not simultaneously
  4. 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() or embedJpg()
  • 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.

Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

HTML

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.

December 31, 2024
JavaScript

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.

December 31, 2024
JavaScript

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.

December 31, 2024