---
title: "Digital PDF signature | Nutrient"
canonical_url: "https://www.nutrient.io/guides/web/samples/javascript-digital-signatures/"
md_url: "https://www.nutrient.io/guides/web/samples/javascript-digital-signatures.md"
last_updated: "2026-06-08T09:14:14.477Z"
description: "Learn to digitally sign any PDF using JavaScript. Access resources and guides for invisible signing and explore code examples for implementation."
---

# Digitally sign a PDF using JavaScript

Digitally sign any PDF even if it doesn’t come with a visible signature form field. Get additional resources by visiting our [JavaScript PDF signature library](/guides/web/signatures.md) or by reading our guide about [invisibly signing any PDF using JavaScript](/guides/web/user-interface/signatures/invisible-signing.md).

[Get Started](https://www.nutrient.io/sdk/web/getting-started.md)

[All Samples](https://www.nutrient.io/guides/web/samples.md)

[Download](https://www.nutrient.io/guides/web/downloads.md)

[Launch Demo](https://www.nutrient.io/demo/)

---

### INDEX.JS

```js

// Since implementations are different depending on the deployment
// we splitted the specific implementation details of each backend
// on its own module. We load them both even though we only need
// the one from the current backend to avoid waiting to dynamically
// fetch and load the relevant implementation.
import standalone from "./standalone.js";
import server from "./server.js";

export async function load(defaultConfiguration) {
  // PSPDFKit.Configuration#authPayload is a Server only property.

  // We check for its presence to determine which module to use
  const deployment = defaultConfiguration.authPayload? server : standalone;

  return deployment(defaultConfiguration);
}

export async function attachListeners(instance, buttons) {
  instance.addEventListener("document.change", async () => {
    // Enable the "Reset" button if the document is signed
    const annotations = await instance.getAnnotations(0);

    updateToolbarItems(annotations, true, instance, buttons);
  });

  instance.addEventListener(
    "annotations.create",
    async (createdAnnotations) => {
      const signatures = await instance.getSignaturesInfo();
      const isDocumentSigned = signatures.status!== "not_signed";

      if (isDocumentSigned) {
        // Bailing out since we just need to handle the scenario before a digital signature
        // has been placed.
        return;
      }

      const annotation = createdAnnotations.get(0);

      if (annotation.isSignature) {
        // Position the created signature over signature form field
        // and enable the "Finish Signing" button.
        positionSignature(annotation, instance);
        await instance.ensureChangesSaved(annotation);
        updateToolbarItems(createdAnnotations, true, instance, buttons);
      }
    }
  );

  instance.addEventListener("annotations.delete", (deletedAnnotations) => {
    // Disable the "Finish Signing" button if a signature has been deleted
    updateToolbarItems(deletedAnnotations, false, instance, buttons);
  });

  const annotations = await instance.getAnnotations(0);

  updateToolbarItems(annotations, true, instance, buttons);

  return instance;
}

// Checks what toolbar items need to be enabled/disabled at a given time.
// When the document doesn't have any electronic signature yet, we want to
// disable the "Finish signing" button. Once a signature is placed, it
// the electronic signature button should be disabled and the "Finish signing"
// should be enabled instead.
// Once the document has been signed, we show the "Reset" button.
async function updateToolbarItems(
  annotations,
  disableFinishIfNoAnnotations,
  instance,
  buttons
) {
  const signatures = await instance.getSignaturesInfo();
  const { resetButton, saveButton, finishButton } = buttons;
  const hasSignatureAnnotation = annotations.some(
    (annotation) => annotation.isSignature
  );
  // When the document is loaded and when a signature annotation is
  // created or deleted, we need to enable or disable the signing custom
  // toolbar item accordingly. The "disableFinishIfNoAnnotations" boolean
  // will determine which disable state we'll update the toolbar button with.
  const shouldDisableFinishBtn = disableFinishIfNoAnnotations?!hasSignatureAnnotation
    : hasSignatureAnnotation;
  const additionalButtons =
    signatures.status === "not_signed"? [
          {
            type: "signature",
            disabled:!shouldDisableFinishBtn,
          },
          {...finishButton,
            disabled: shouldDisableFinishBtn,
          },
          saveButton,
        ]
      : [{ type: "signature", disabled: true }, resetButton, saveButton];

  instance.setToolbarItems([...initialToolbarItems,...additionalButtons]);
}

// Helper function to properly place the signature annotation
// added by the user to the corresponding spot on the document.
// Based on https://www.nutrient.io/guides/web/knowledge-base/override-ink-signature-dialog/
function positionSignature(annotation, instance) {
  // appropiate rect for the space to fit the annotation in
  const signingSpace = new PSPDFKit.Geometry.Rect({
    width: 150,
    height: 40,
    left: 375,
    top: 690,
  });

  const newSize = fitIn(
    {
      width: annotation.boundingBox.width,
      height: annotation.boundingBox.height,
    },
    {
      width: signingSpace.width,
      height: signingSpace.height,
    }
  );
  const resizeRatio = newSize.width / annotation.boundingBox.width;
  const newLeft =
    signingSpace.left + signingSpace.width / 2 - newSize.width / 2;
  const newTop =
    signingSpace.top + signingSpace.height / 2 - newSize.height / 2;

  const newBoundingBox = new PSPDFKit.Geometry.Rect({
    left: newLeft,
    top: newTop,
    width: newSize.width,
    height: newSize.height,
  });

  if (annotation.lines) {
    const newLines = annotation.lines.map((line) => {
      return line.map((point) => {
        return new PSPDFKit.Geometry.DrawingPoint({
          x: newLeft + (point.x - annotation.boundingBox.left) * resizeRatio,
          y: newTop + (point.y - annotation.boundingBox.top) * resizeRatio,
        });
      });
    });

    instance.update(
      annotation.set("boundingBox", newBoundingBox).set("lines", newLines).set("lineWidth", annotation.lineWidth * resizeRatio)
    );
  } else {
    instance.update(annotation.set("boundingBox", newBoundingBox));
  }
}

function fitIn(size, containerSize) {
  const { width, height } = size;

  const widthRatio = containerSize.width / width;
  const heightRatio = containerSize.height / height;

  const ratio = Math.min(widthRatio, heightRatio);

  return {
    width: width * ratio,
    height: height * ratio,
  };
}

export const initialToolbarItems = [
  { type: "sidebar-thumbnails" },
  { type: "sidebar-bookmarks" },
  { type: "zoom-in" },
  { type: "zoom-out" },
  { type: "spacer" },
];

```

### SERVER.JS

```js

import PSPDFKit from "@nutrient-sdk/viewer";
import { attachListeners, initialToolbarItems } from "./index";

let instance = null;

const buttons = {
  saveButton: null,
  finishButton: {
    type: "custom",
    title: "Finish Signing",
    className: "finish-signing",
    name: "sign",
    async onPress() {
      // When "Finish Signing" is pressed, after the user
      // has added an ink signature, we proceed to apply
      // a digital signature to the document. From this
      // point on the integrity of the file is guaranteed.
      try {
        await instance.signDocument(null, {
          // The example signing microservice we are using
          // expects the "user-1-with-rights" token when
          // invoking its endpoint. Nutrient Document Engine forwards
          // any value specified in "signingToken" to it.
          signingToken: "user-1-with-rights",
        });
        console.log("New signature added to the document!");
      } catch (error) {
        console.error(error);
      }
    },
  },
  resetButton: {
    type: "custom",
    title: "Reset",
    name: "reset",
    async onPress() {
      localStorage.removeItem(
        "examples/digital-signatures-sign/lastUsedServerDocumentId"
      );
      location.href = "/digital-signatures-sign";
    },
  },
};

export default function load(defaultConfiguration) {
  const { toolbarItems } = defaultConfiguration;

  // split the rest of the toolbar items from the save button so that
  // later we can keep it as the last item while adding the sign button
  buttons.saveButton = toolbarItems[toolbarItems.length - 1];

  return PSPDFKit.load({...defaultConfiguration,
    toolbarItems: initialToolbarItems,
    styleSheets: ["/digital-signatures-sign/static/styles.css"],
    initialViewState: new PSPDFKit.ViewState({
      showSignatureValidationStatus:
        PSPDFKit.ShowSignatureValidationStatusMode.IF_SIGNED,
    }),
  }).then(async (_instance) => {
    instance = _instance;
    console.log("Nutrient Web SDK successfully loaded!!", instance);
    attachListeners(instance, buttons);

    return instance;
  });
}

```

### STANDALONE.JS

```js

import PSPDFKit from "@nutrient-sdk/viewer";
import { attachListeners, initialToolbarItems } from "./index";

let forge = null;
let instance = null;

const buttons = {
  saveButton: null,
  finishButton: {
    type: "custom",
    title: "Finish Signing",
    className: "finish-signing",
    name: "sign",
    async onPress() {
      // When "Finish Signing" is pressed, after the user
      // has added an ink signature, we proceed to apply
      // a digital signature to the document. From this
      // point on the integrity of the file is guaranteed.
      try {
        await instance.signDocument(null, generatePKCS7);
        console.log("New signature added to the document!");
      } catch (error) {
        console.error(error);
      }
    },
  },
  resetButton: {
    type: "custom",
    title: "Reset",
    name: "reset",
    async onPress() {
      location.href = "/digital-signatures-sign";
    },
  },
};

export default function load(defaultConfiguration) {
  const { toolbarItems } = defaultConfiguration;

  import("./static/forge.min.js").then(({ default: _forge }) => {
    forge = _forge;
  });

  // split the rest of the toolbar items from the save button so that
  // later we can keep it as the last item while adding the sign button
  buttons.saveButton = toolbarItems[toolbarItems.length - 1];

  return PSPDFKit.load({...defaultConfiguration,
    toolbarItems: initialToolbarItems,
    styleSheets: ["/digital-signatures-sign/static/styles.css"],
    initialViewState: new PSPDFKit.ViewState({
      showSignatureValidationStatus:
        PSPDFKit.ShowSignatureValidationStatusMode.IF_SIGNED,
    }),
    async trustedCAsCallback() {
      // The particular certificate + private key that we are going to use
      // for signing this example were issued by this CA that we are going
      // to use for validation after signing.
      const response = await fetch("/digital-signatures-sign/static/ca.pem");
      const cert = await response.text();

      return [cert];
    },
  }).then(async (_instance) => {
    instance = _instance;
    console.log("Nutrient Web SDK successfully loaded!!", instance);
    attachListeners(instance, buttons);

    return instance;
  });
}

// Naive implementation that fetches the private key over the network.
// Do not use it for a production deployment.
async function generatePKCS7({ fileContents }) {
  const certificatePromise = fetch(
    "/digital-signatures-sign/static/certificate.pem"
  ).then((response) => response.text());
  const privateKeyPromise = fetch(
    "/digital-signatures-sign/static/private-key.pem"
  ).then((response) => response.text());
  const [certificatePem, privateKeyPem] = await Promise.all([
    certificatePromise,
    privateKeyPromise,
  ]);
  const certificate = forge.pki.certificateFromPem(certificatePem);
  const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);

  const p7 = forge.pkcs7.createSignedData();

  p7.content = new forge.util.ByteBuffer(fileContents);
  p7.addCertificate(certificate);
  p7.addSigner({
    key: privateKey,
    certificate: certificate,
    digestAlgorithm: forge.pki.oids.sha256,
    authenticatedAttributes: [
      {
        type: forge.pki.oids.contentType,
        value: forge.pki.oids.data,
      },
      {
        type: forge.pki.oids.messageDigest,
      },
      {
        type: forge.pki.oids.signingTime,
        value: new Date(),
      },
    ],
  });

  p7.sign({ detached: true });

  return stringToArrayBuffer(forge.asn1.toDer(p7.toAsn1()).getBytes());
}

// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
function stringToArrayBuffer(binaryString) {
  const buffer = new ArrayBuffer(binaryString.length);
  let bufferView = new Uint8Array(buffer);

  for (let i = 0, len = binaryString.length; i < len; i++) {
    bufferView[i] = binaryString.charCodeAt(i);
  }

  return buffer;
}

```

This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.

---

## Related pages

- [Add watermarks to PDFs using JavaScript example](/guides/web/samples/add-watermarks-to-pdf-javascript.md)
- [Customize PDF annotation permissions using JavaScript](/guides/web/samples/custom-annotation-permissions.md)
- [PDF Collaboration permissions using JavaScript](/guides/web/samples/collaboration-permissions.md)
- [Customize the UI for PDF annotations using JavaScript](/guides/web/samples/annotations-inspector.md)
- [JavaScript PDF magazine viewer](/guides/web/samples/javascript-magazine-viewer.md)
- [Add electronic signature images to PDFs using JavaScript](/guides/web/samples/adding-image-electronic-signatures.md)
- [Hide or reveal area on PDFs using JavaScript](/guides/web/samples/hide-reveal-area-in-pdf.md)
- [Customize PDF annotation tooltips using JavaScript](/guides/web/samples/custom-annotation-tooltip.md)
- [PDF annotation in JavaScript](/guides/web/samples/javascript-pdf-annotations.md)
- [Redact PDFs using JavaScript](/guides/web/samples/javascript-pdf-redaction.md)
- [Open PDFs using JavaScript](/guides/web/samples/open-pdf-using-javascript.md)
- [PDF presentation mode using JavaScript](/guides/web/samples/presentation-mode.md)
- [View PDFs in dark mode using JavaScript](/guides/web/samples/dark-mode-pdf-viewer.md)
- [Customizing JavaScript PDF printing modes](/guides/web/samples/pdf-printing-modes.md)
- [Customized Document Editor Toolbar](/guides/web/samples/customized-document-editor-toolbar.md)
- [PDF text selection using JavaScript](/guides/web/samples/pdf-text-selection-javascript.md)
- [Drag-and-drop UI in our JavaScript PDF viewer](/guides/web/samples/drag-and-drop.md)
- [Zoom example for our JavaScript PDF viewer](/guides/web/samples/zooming.md)
- [Open, view, and annotate on images using JavaScript](/guides/web/samples/annotating-images.md)
- [Handling password-protected PDFs in our JavaScript viewer](/guides/web/samples/password-protected-pdf.md)
- [Collaborate on PDFs using JavaScript](/guides/web/samples/instant-pdf-collaboration.md)
- [PDF form support using JavaScript](/guides/web/samples/javascript-pdf-form.md)
- [Create custom overlays on PDFs using JavaScript](/guides/web/samples/custom-overlay-items.md)
- [Custom HTML PDF annotations using JavaScript](/guides/web/samples/custom-annotations.md)
- [Add electronic signatures to PDFs using JavaScript](/guides/web/samples/electronic-signatures-in-pdf.md)
- [Customize the PDF toolbar using JavaScript](/guides/web/samples/customized-pdf-toolbar.md)
- [Customizing PDF text search using JavaScript](/guides/web/samples/customized-pdf-search.md)
- [Flipbook PDF viewer using JavaScript](/guides/web/samples/flipbook.md)
- [Disable PDF editing and annotations](/guides/web/samples/open-read-only-pdf.md)
- [Edit PDFs using JavaScript](/guides/web/samples/edit-pdf-javascript.md)
- [Storing electronic signatures in the browser using JavaScript](/guides/web/samples/stored-electronic-signatures.md)

