JavaScript PDF editor tutorial using pdf‑lib

Table of contents

    This tutorial will guide you through building a simple JavaScript PDF editor with pdf-lib. Then, it’ll show how to deploy your editor in Nutrient’s JavaScript PDF viewer.
    JavaScript PDF editor tutorial using pdf‑lib
    TL;DR

    This tutorial compares two approaches for building JavaScript PDF editors: pdf-lib (open source), and Nutrient (commercial). Use pdf-lib for basic PDF operations like adding/removing pages, drawing text, embedding images, and filling forms in any JavaScript environment. Choose Nutrient for enterprise applications requiring advanced features like annotations, digital signatures, and real-time collaboration, plus a ready-to-use UI. The tutorial provides code examples for implementing core PDF editing functionality with pdf-lib and demonstrates how to integrate with Nutrient for enhanced capabilities.

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

    Introduction to pdf-lib

    pdf-lib(opens in a new tab) 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(opens in a new tab) 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, you’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

    1. First, you need to have:

    2. Install the @nutrient-sdk/viewer package via npm:

      Terminal window
      npm i @nutrient-sdk/viewer
    3. Copy the required viewer artifacts to your assets directory:

      Terminal window
      cp -R ./node_modules/@nutrient-sdk/viewer/dist/ ./assets/

    Make sure the assets directory contains:

    • nutrient-viewer.js (or an equivalent main script)
    • A nutrient-viewer-lib/ directory with supporting library files

    Integrating into your project

    1. Include a sample document (e.g. document.pdf) in the public or root folder of your project. You can use our demo image as an example.

    2. Add a mounting <div> and a script reference to your HTML:

      <div id="nutrient" style="width: 100%; height: 100vh;"></div>
      <script type="module" src="index.js"></script>
    3. Import the viewer in your JavaScript entry file:

      import "./assets/nutrient-viewer.js";
    4. Initialize the viewer using NutrientViewer.load():

      const baseUrl = `${window.location.protocol}//${window.location.host}/assets/`;
      NutrientViewer.load({
      baseUrl,
      container: "#nutrient",
      document: "document.pdf",
      })
      .then((instance) => {
      console.log("Nutrient loaded", instance);
      })
      .catch((error) => {
      console.error(error.message);
      });

    Serving your website

    1. Install the serve package globally:

      Terminal window
      npm install --global serve
    2. Start a local server from the current directory:

      Terminal window
      serve -l 8080 .
    3. Open your browser and go to http://localhost:8080 to view your website.

    And that’s it! All the features you built using pdf-lib are already present out of the box in our SDK, so you 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

    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.

    Veronica Marini

    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.

    Hulya Masharipov

    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.

    Explore related topics

    FREE TRIAL Ready to get started?