> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/digital-signature/sign-pdf.md).

# Sign PDFs and manage signing workflows using JavaScript

Learn how to digitally sign PDF files with the Apryse SDK. Add approval signatures using a Digital Certificate for authenticity and security. Get started now! The Apryse Web SDK streamlines secure, se

For more advanced controls - including document permissions, custom signing for HSM integration, and LTV/time stamping - please refer to the sample below: [Digitally sign PDF files](/web/get-started/samples/digitalsignaturestest.md)

{% hint style="info" %}
**Requirements**

The following packages are required to use these features in production. Trial keys have unlimited access to all features.

<a href="https://apryse.com/capabilities#DigitalSignature" class="button primary">Package: Digital Signature</a><a href="https://showcase.apryse.com/digital-signatures" class="button primary">Live demo</a>
{% endhint %}

WebViewer provides APIs to create smooth signing workflows. You can easily create fields, assign users to specific fields, and help them navigate the form and sign it. These APIs are flexible, so you can create customized signing apps for your needs.

<div align="left"><figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2FxBaUmWHeILHbZsb8H33w%2Fd13ec57d8d0b4c371e4cd243d4f9fd1a3a1e8b1e-2892x1598.png?alt=media&amp;token=206dd6ac-b1a3-4fd4-9c0c-287501b2d23c" alt="A flowchart diagram displaying 4 steps. Step 1 is &#x22;Create: Create your form&#x22; and shows a document with 6 gray fields. Step 2 is &#x22;Assign: Assign any field to any user&#x22; and shows the same document with the odd fields blue and assigned to John, while the even fields are orange and assigned to Mary. Step 3 is &#x22;Jump: Navigate between fields, e.g. for each user&#x22; and shows the same document with a line drawn between field 1 and 3. Step 4 is &#x22;Sign: Fill and sign fields&#x22; which shows the document with all of John&#x27;s fields signed by John."><figcaption><p>A potential use case of the WebViewer signing APIs</p></figcaption></figure></div>

The fields in these workflows can hold e-signatures or digital signatures. For the difference between them, and how certificates and certificate authorities work, see [Digital signature vs. e-signature](/web/digital-signature/signature.md#digital-signature-vs-e-signature).

## Assign fields to users

By default, anyone can sign any field. In practice, you often need to limit fields to specific users. You can use `Field.setUser(id)` and `FieldManager.getFieldsForUser(id)` APIs to assign a user to a field and get a list of fields for a user. You can then update field permissions to allow only that user to sign it.

{% hint style="info" %}
**Info:** The `Field.setUser`, `Field.getUser`, and `FieldManager.getFieldsForUser` APIs were added in WebViewer v12.1.0.
{% endhint %}

{% tabs %}
{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
WebViewer({
  // ...Config options
}).then(async (instance) => {
  const { Annotations, annotationManager, documentViewer } = instance.Core;
  const { fieldManager } = annotationManager;
  const { WidgetFlags } = Annotations;

  const createTextField = (user: string, fieldName: string, value: string, page: number, x: number, y: number) => {
      const flags = new WidgetFlags();
      flags.set(WidgetFlags.REQUIRED, true);
      flags.set(WidgetFlags.MULTILINE, true);
      const field = new Annotations.Forms.Field(fieldName, {
          type: 'Tx',
          defaultValue: value,
          flags,
      });
      const widgetAnnot = new Annotations.TextWidgetAnnotation(field);
      widgetAnnot.PageNumber = page;
      widgetAnnot.X = x;
      widgetAnnot.Y = y;
      widgetAnnot.Width = 150;
      widgetAnnot.Height = 50;
      field.setUser(user);
      annotationManager.getFieldManager().addField(field);
      annotationManager.addAnnotation(widgetAnnot);
      annotationManager.drawAnnotationsFromList([widgetAnnot]);
  };

  const createSignatureField = (user: string, fieldName: string, value: string, page: number, x: number, y: number) => {
      const flags = new WidgetFlags();
      flags.set(WidgetFlags.REQUIRED, true);
      flags.set(WidgetFlags.MULTILINE, true);
      const field = new Annotations.Forms.Field(fieldName, {
        type: 'Sig',
        defaultValue: value,
        flags,
      });
      const widgetAnnot = new Annotations.SignatureWidgetAnnotation(field);
      widgetAnnot.PageNumber = page;
      widgetAnnot.X = x;
      widgetAnnot.Y = y;
      widgetAnnot.Width = 150;
      widgetAnnot.Height = 50;
      field.setUser(user);
      annotationManager.getFieldManager().addField(field);
      annotationManager.addAnnotation(widgetAnnot);
      annotationManager.drawAnnotationsFromList([widgetAnnot]);
  };

  createTextField('John', 'TextFieldJohn', 'Full Name', 1, 100, 100);
  createSignatureField('John', 'SigFieldJohn', 'Signature', 2, 100, 100);
  createTextField('Mary', 'TextFieldMary', 'Full Name', 1, 100, 300);
  createSignatureField('Mary', 'SigFieldMary', 'Signature', 2, 100, 300);
  const johnFields = fieldManager.getFieldsForUser('John');
  const maryFields = fieldManager.getFieldsForUser('Mary');

  const users = ['John', 'Mary'];
  let currentUser = 'John';
  
  const updateFieldPermission = () => {
    // Option 1: Use getFieldsForUser to update all fields for a specific user
    for (const field of johnFields) {
      field?.widgets?.map(annot => {
        annot.fieldFlags.set('ReadOnly', currentUser !== 'John');
      });
    }
    for (const field of maryFields) {
      field?.widgets?.map(annot => {
        annot.fieldFlags.set('ReadOnly', currentUser !== 'Mary');
      });
    }

    // Option 2: Iterate through all fields
    const allFields = fieldManager.getFields();
    for (const field of allFields) {
      if (field?.getUser?.() === currentUser) {
        field?.widgets?.map(annot => {
          annot.fieldFlags.set('ReadOnly', false);
        });
      } else {
        field?.widgets?.map(annot => {
          annot.fieldFlags.set('ReadOnly', true);
        });
      }
    }
  }
  
  for (const user of users) {
    const button = document.getElementById(user + '-button');
    button?.addEventListener('click', () => {
      currentUser = user;
      updateFieldPermission();
    });
  }
  updateFieldPermission();
});
```

{% endcode %}

**Related links**

* [setUser](https://sdk.apryse.com/api/web/Core.Annotations.Forms.Field.html#setUser)
* [getUser](https://sdk.apryse.com/api/web/Core.Annotations.Forms.Field.html#getUser)
* [getFieldsForUser](https://sdk.apryse.com/api/web/Core.Annotations.Forms.FieldManager.html#getFieldsForUser)
  {% endtab %}
  {% endtabs %}

## Jump to a field

To make the viewer jump to a specific field, you can use the `FieldManager.jumpToField` API. The API accepts options to adjust how the viewer will jump. This feature can be leveraged to automatically scroll a user between fields in a signing workflow. See [Digital signature samples using JavaScript](/web/digital-signature/samples.md).

{% tabs %}
{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
WebViewer({
    // ...Config options
  }).then(async (instance) => {
    const { documentViewer, Annotations, annotationManager } = instance.Core;
    const { fieldManager } = annotationManager;
    const { WidgetFlags } = Annotations;
      const createSignatureField = (user: string, fieldName: string, value: string, page: number, x: number, y: number) => {
      const flags = new WidgetFlags();
      flags.set(WidgetFlags.REQUIRED, true);
      flags.set(WidgetFlags.MULTILINE, true);
      const field = new Annotations.Forms.Field(fieldName, {
        type: 'Sig',
        defaultValue: value,
        flags,
      });
      const widgetAnnot = new Annotations.SignatureWidgetAnnotation(field);
      widgetAnnot.PageNumber = page;
      widgetAnnot.X = x;
      widgetAnnot.Y = y;
      widgetAnnot.Width = 150;
      widgetAnnot.Height = 50;
      field.setUser(user);
      annotationManager.getFieldManager().addField(field);
      annotationManager.addAnnotation(widgetAnnot);
      annotationManager.drawAnnotationsFromList([widgetAnnot]);
      return field;
    };

    documentViewer.addEventListener('documentLoaded', async () => {
      const field = createSignatureField('John', 'SigFieldJohn', 'Signature', 2, 100, 100);
      const jumpOptions = {
        zoom: 1,
        isSmoothScroll: true,
      };
      await fieldManager.jumpToField(field, jumpOptions);
    });
  });
```

{% endcode %}

**Related links**

* [jumpToField](https://sdk.apryse.com/api/web/Core.Annotations.Forms.FieldManager.html#jumpToField)
  {% endtab %}
  {% endtabs %}

## Digitally sign a field

{% hint style="warning" %}
**Warning:** Make sure you have [Full API enabled in WebViewer.](/web/what-is-webviewer/full-api.md)
{% endhint %}

To sign an existing approval signature field in a PDF document:

{% tabs %}
{% tab title="JavaScript (SDK v11.12+)" %}
{% code lineNumbers="true" %}

```js
WebViewer({
  fullAPI: true,
  // ...Other config options
}).then(async (instance) => {
  /* This example assumes:
   * - The certificates are hosted from `your-test-env.com/digital-sig/certificate_x.pfx` and `your-test-env.com/digital-sig/cert_x.pem`
   * * - `.pem` files should contain public certificates
   * → used to verify signatures
   *
   * - `.pfx` files contain private key + certificate (PKCS#12 bundle)
   * → used to create digital signatures (requires password)
   */

  const { Annotations, documentViewer } = instance.Core;
  const { VerificationOptions } = instance.UI;
  const annotationManager = documentViewer.getAnnotationManager();
  const fieldManager = annotationManager.getFieldManager();

  // 1. Add trust certificates (PUBLIC certs only)
  VerificationOptions.addTrustedCertificates([
    '/digital-sig/cert_1.pem',
    '/digital-sig/cert_2.pem',
    '/digital-sig/cert_3.pem',
    '/digital-sig/cert_4.pem',
    '/digital-sig/cert_5.pem',
  ]);
  console.log('🔒 Trust certificates added');

  // 2. Listen for signature application events
  annotationManager.addEventListener('digitalSignatureApplied', (details) => {
    console.log('SignatureWidgetInfo From Event: ', details);
  });

  // 3. Create signature fields
  const sigConfigs = [
    { name: 'Sig_1' + Math.random(), page: 1, x: 50, y: 50, w: 200, h: 50 },
    { name: 'Sig_2' + Math.random(), page: 1, x: 300, y: 50, w: 200, h: 50 },
    { name: 'Sig_3' + Math.random(), page: 1, x: 50, y: 120, w: 200, h: 50 },
    { name: 'Sig_4' + Math.random(), page: 1, x: 300, y: 120, w: 200, h: 50 },
    { name: 'Sig_5' + Math.random(), page: 1, x: 50, y: 190, w: 200, h: 50 },
  ];

  const widgets = [];
  for (const cfg of sigConfigs) {
    const field = new Annotations.Forms.Field(cfg.name, { type: 'Sig' });
    fieldManager.addField(field);

    const widget = new Annotations.SignatureWidgetAnnotation(field, {
      appearance: '_DEFAULT',
      appearances: { _DEFAULT: { Normal: { offset: {} } } },
    });
    widget.PageNumber = cfg.page;
    widget.X = cfg.x;
    widget.Y = cfg.y;
    widget.Width = cfg.w;
    widget.Height = cfg.h;
    widgets.push(widget);
  }
  annotationManager.addAnnotations(widgets);
  annotationManager.drawAnnotationsFromList(widgets);
  console.log('📝 Created 5 signature fields');

  // 4. Fetch the cryptographic PFX bundles
  const certs = await Promise.all(
    [1, 2, 3, 4, 5].map(async (i) => {
      const resp = await fetch(`/digital-sig/certificate_${i}.pfx`);
      const buf = await resp.arrayBuffer();
      console.log(`📄 Loaded certificate_${i}.pfx (${buf.byteLength} bytes)`);
      return buf;
    })
  );

  // 5. Apply signatures sequentially
  let signedData;
  for (let i = 0; i < sigConfigs.length; i++) {
    const fieldName = sigConfigs[i].name;
    try {
      console.log(`🖊️ Signing "${fieldName}" with certificate_${i + 1}.pfx...`);
      signedData = await annotationManager.sign(fieldName, {
        certificate: certs[i],
        password: 'test123',
      });
      console.log(`"${fieldName}" signed!`);
    } catch (err) {
      console.error(`Failed to sign "${fieldName}":`, err);
      break;
    }
  }

  console.log('All 5 signatures applied!');

  // 6. Save the signed document
  // sign() resolves to { buffer, fieldName, widget }. Save the buffer —
  // the document open in the viewer is not the signed file.
  const blob = new Blob([signedData.buffer], { type: 'application/pdf' });
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = 'signed_doc.pdf';
  link.click();
  URL.revokeObjectURL(url);
});
```

{% endcode %}

**Related links**

* [WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html)
  {% endtab %}

{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer({
  fullAPI: true,
  // ...Other config options
}).then(instance => {
  const { PDFNet, documentViewer } = instance.Core;

  documentViewer.addEventListener('documentLoaded', () => {
    await PDFNet.initialize();
    const doc = await documentViewer.getDocument().getPDFDoc();

    // Run PDFNet methods with memory management
    await PDFNet.runWithCleanup(async () => {

      // lock the document before a write operation
      // runWithCleanup will auto-unlock when complete
      doc.lock();

      // Add an StdSignatureHandler instance to PDFDoc, making sure to keep track of it using the ID returned.
      const sigHandlerId = await doc.addStdSignatureHandlerFromURL(cert_file_path, 'password');

      // Retrieve the unsigned approval signature field.
      /**
       * Note: Replace approvalFieldName with the field name in the document
       * that is being signed and approved
       */
      const foundApprovalField = await doc.getField(approvalFieldName);
      const approvalSigField = await PDFNet.DigitalSignatureField.createFromField(foundApprovalField);

      // (OPTIONAL) Add more information to the signature dictionary.
      await approvalSigField.setLocation("Vancouver, BC");
      await approvalSigField.setReason("Document approval.");
      await approvalSigField.setContactInfo("www.apryse.com");

      // (OPTIONAL) Add an appearance to the signature field.
      const img = await PDFNet.Image.createFromURL(doc, appearance_img_path);
      const approvalSignatureWidget = await PDFNet.SignatureWidget.createWithDigitalSignatureField(doc, await PDFNet.Rect.init(0, 100, 200, 150), approvalSigField);
      await approvalSignatureWidget.createSignatureAppearance(img);
      const page1 = await doc.getPage(1);
      page1.annotPushBack(approvalSignatureWidget);

      // Prepare the signature and signature handler for signing.
      await approvalSigField.signOnNextSaveWithCustomHandler(sigHandlerId);

      // The actual approval signing will be done during the save operation.
      const buf = await doc.saveMemoryBuffer(0);
      const blob = new Blob([buf], { type: 'application/pdf' });
      saveAs(blob, 'signed_doc.pdf');
    });
  })
})
```

{% endcode %}

**Related links**

* [WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html)
* [PDFDoc.createFromURL](https://sdk.apryse.com/api/web/Core.PDFNet.PDFDoc.html#.createFromURL__anchor)
* [PDFDoc.addStdSignatureHandlerFromURL](https://sdk.apryse.com/api/web/Core.PDFNet.PDFDoc.html#addStdSignatureHandlerFromURL__anchor)
* [PDFDoc.getField](https://sdk.apryse.com/api/web/Core.PDFNet.PDFDoc.html#getField__anchor)
* [Field.useSignatureHandler](https://sdk.apryse.com/api/web/Core.PDFNet.Field.html#useSignatureHandler__anchor)
* [PDFNet.Obj.putName](https://sdk.apryse.com/api/web/Core.PDFNet.Obj.html#putName__anchor)
* [PDFNet.Obj.putString](https://sdk.apryse.com/api/web/Core.PDFNet.Obj.html#putString__anchor)
  {% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer({
  fullAPI: true,
  // ...Other config options
}).then(instance => {
  const { PDFNet, docViewer } = instance;

  docViewer.on('documentLoaded', () => {
    await PDFNet.initialize();
    const doc = await docViewer.getDocument().getPDFDoc();

    // Run PDFNet methods with memory management
    await PDFNet.runWithCleanup(async () => {

      // lock the document before a write operation
      // runWithCleanup will auto-unlock when complete
      doc.lock();

      // Add an StdSignatureHandler instance to PDFDoc, making sure to keep track of it using the ID returned.
      const sigHandlerId = await doc.addStdSignatureHandlerFromURL(cert_file_path, 'password');

      // Retrieve the unsigned approval signature field.
      /**
       * Note: Replace approvalFieldName with the field name in the document
       * that is being signed and approved
       */
      const foundApprovalField = await doc.getField(approvalFieldName);
      const approvalSigField = await PDFNet.DigitalSignatureField.createFromField(foundApprovalField);

      // (OPTIONAL) Add more information to the signature dictionary.
      await approvalSigField.setLocation("Vancouver, BC");
      await approvalSigField.setReason("Document approval.");
      await approvalSigField.setContactInfo("www.apryse.com");

      // (OPTIONAL) Add an appearance to the signature field.
      const img = await PDFNet.Image.createFromURL(doc, appearance_img_path);
      const approvalSignatureWidget = await PDFNet.SignatureWidget.createWithDigitalSignatureField(doc, await PDFNet.Rect.init(0, 100, 200, 150), approvalSigField);
      await approvalSignatureWidget.createSignatureAppearance(img);
      const page1 = await doc.getPage(1);
      page1.annotPushBack(approvalSignatureWidget);

      // Prepare the signature and signature handler for signing.
      await approvalSigField.signOnNextSaveWithCustomHandler(sigHandlerId);

      // The actual approval signing will be done during the save operation.
      const buf = await doc.saveMemoryBuffer(0);
      const blob = new Blob([buf], { type: 'application/pdf' });
      saveAs(blob, 'signed_doc.pdf');
    });
  })
})
```

{% endcode %}

**Related links**

* [WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [PDFDoc.createFromURL](https://sdk.apryse.com/api/web/Core.PDFNet.PDFDoc.html#.createFromURL__anchor)
* [PDFDoc.addStdSignatureHandlerFromURL](https://sdk.apryse.com/api/web/Core.PDFNet.PDFDoc.html#addStdSignatureHandlerFromURL__anchor)
* [PDFDoc.getField](https://sdk.apryse.com/api/web/Core.PDFNet.PDFDoc.html#getField__anchor)
* [Field.useSignatureHandler](https://sdk.apryse.com/api/web/Core.PDFNet.Field.html#useSignatureHandler__anchor)
* [PDFNet.Obj.putName](https://sdk.apryse.com/api/web/Core.PDFNet.Obj.html#putName__anchor)
* [PDFNet.Obj.putString](https://sdk.apryse.com/api/web/Core.PDFNet.Obj.html#putString__anchor)
  {% endtab %}
  {% endtabs %}

{% hint style="info" %}
**Info**

The signed PDF is in the buffer returned by the signing call. Save or download this buffer, as shown in the previous code sample. The document opened in the viewer is not the signed file. Downloading the document from the viewer, for example with the Download button or `downloadPdf()`, produces a copy whose signatures fail validation.
{% endhint %}

For more advanced controls - including document permissions, custom signing for HSM integration, and LTV/time stamping - please refer to the sample below: [Digitally sign PDF files](/web/get-started/samples/digitalsignaturestest.md)

## About approval signatures

The Apryse SDK enables approval signatures in PDF documents using a Digital Certificate, in accordance with the latest PDF specification.

By leveraging [public key infrastructure (PKI)](https://en.wikipedia.org/wiki/Public_key_infrastructure) technology, with a certificate issued by a trusted [certificate authority (CA)](https://en.wikipedia.org/wiki/Certificate_authority), a signer can use a certificate-based digital ID to guarantee a signature's authenticity. A certificate-based digital signature can also ensure that a document has not been modified since the signature was applied, preserving its authenticity.

When you open a signed PDF in WebViewer, the Signatures panel shows the verification result for each signature.

<div align="left"><figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-51c4b698c1ec2efcc0d46686d1342347626f1a9d%2F8130e2be0155c3a56bbf8548f836090a8beb0ac2-299x658.png?alt=media" alt="WebViewer&#x27;s Signatures panel showing two verified signatures, one approval signature and one certification signature from http://www.pdftron.com, both marked valid and trusted."><figcaption><p>Verified signatures in WebViewer's Signatures panel.</p></figcaption></figure></div>

In this example, the first is an approval signature (**Signed**) and the second is a certification signature (**Certified by**…).

## Next steps

You can use the APIs above to build a custom document signing app that is quick, easy, and secure. Users can be assigned to specific fields, automatically scroll to the next field, and finalize their changes with confidence in the document's authenticity.

For a sample implementation, see [Digital signature samples using JavaScript](/web/digital-signature/samples.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.apryse.com/web/digital-signature/sign-pdf.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
