DocGen & eSign

Nutrient PDF Web Viewer - Technical Guide

Overview

Our application uses the Nutrient Web SDK for two primary document-processing capabilities:

  1. PDF Forms — Load PDFs, access form fields and widget annotations, update field properties, and support electronic signatures.

  2. Word Templates — Load DOCX templates, populate placeholders using a JSON model, and convert the populated document to PDF.

The overall architecture is:

                    Nutrient Web SDK
                           |
             +-------------+-------------+
             |                           |
        PDF Forms                  Word Templates
             |                           |
      PDF + Form Fields          DOCX + JSON Model
             |                           |
      Field / Widget APIs       populateDocumentTemplate()
             |                           |
             v                           v
       Updated PDF              Populated DOCX
                                         |
                                         v
                                  convertToPDF()
                                         |
                                         v
                                    Final PDF

Loading the Nutrient Web SDK

The Web SDK can be loaded through the Nutrient CDN.

HTML
<script src="https://cdn.cloud.nutrient.io/pspdfkit-web@1.20.0/nutrient-viewer.js"></script>

<div id="pdf-viewer" style="width: 100%; height: 100vh;"></div>

The viewer is then initialized using NutrientViewer.load():

JavaScript
 pdfInstance = await NutrientViewer.load({
      container: "#pspdfkit",
       document: base64StringOfDocument,
        licenseKey: licensekey
    });

NutrientViewer.load() returns a viewer instance that provides APIs for interacting with the loaded document.


PDF Forms

3.1 Form Fields vs. Widget Annotations

A PDF form field consists of two related components:

Component

Purpose

Form Field

Stores the field's data and behavior

Widget Annotation

Provides the visual representation of the field on the PDF page

For example:

TextFormField
     |
     | formFieldName
     v
WidgetAnnotation
     |
     v
Visual text input on PDF

Form Field

The form field contains properties such as:

  • name

  • value

  • required

  • readOnly

  • noExport

Widget Annotation

The widget controls the visual appearance and interaction of the field, including:

  • backgroundColor

  • borderColor

  • fontColor

  • borderWidth

  • fontSize

  • opacity

  • horizontalAlign

  • verticalAlign

A form field itself does not have a visual representation. The widget annotation is what is actually rendered on the PDF page.

A single form field can also have multiple widgets. This is commonly seen with radio-button groups.


3.2 Supported Form Field Types

Form Field

Widget Annotation

CheckBoxFormField

WidgetAnnotation

RadioButtonFormField

WidgetAnnotation

ButtonFormField

WidgetAnnotation

ListBoxFormField

WidgetAnnotation

ComboBoxFormField

WidgetAnnotation

TextFormField

WidgetAnnotation

SignatureFormField

WidgetAnnotation


Updating Form Fields

Nutrient uses instance.update() to modify form fields and widget annotations.

For example, making a field required and changing its widget opacity:

const formFields = await instance.getFormFields();

const formField = formFields.find(
  (formField) => formField.name === "my form field"
);

if (!formField) {
  console.warn('Form field "my form field" not found');
  return;
}

const annotations = await instance.getAnnotations(0);

const widget = annotations.find(
  (annotation) => annotation.formFieldName === "my form field"
);

if (!widget) {
  console.warn('Widget for "my form field" not found on page zero');
  return;
}

await instance.update([
  formField.set("required", true),
  widget.set("opacity", 0.5),
]);

Key distinction

formField.set(...)
       ↓
Changes field behavior/data

widget.set(...)
       ↓
Changes field appearance/visual behavior

Batch Updating Fields

Multiple fields can be updated in a single instance.update() call.

JavaScript
const formFields = await instance.getFormFields();
const updates = [];

formFields.forEach((formField) => {
  if (
    formField.name === "customerName" ||
    formField.name === "invoiceNumber"
  ) {
    updates.push(formField.set("readOnly", true));
  }

  if (
    formField.name === "email" ||
    formField.name === "phone"
  ) {
    updates.push(formField.set("required", true));
  }
});

await instance.update(updates);

This approach is preferable to performing separate update operations for every field.


Making Fields Read-Only

A field can be made read-only using the form-field property:

SQL
const formFields = await instance.getFormFields();

if (formFields.size === 0) {
  return;
}

const formField = formFields.first();

await instance.update(
  formField.set("readOnly", true)
);

Setting:

readOnly = true

prevents the field value from being modified by the user.

Widget-level restrictions can also be used when interaction needs to be controlled at the annotation level.


Signature Fields

A SignatureFormField represents a designated signing area within the PDF.

Nutrient supports:

  • Ink signature — user draws the signature

  • Image signature — user uploads an image

  • Typed signature — user enters a text-based signature

A signature field is different from simply creating an ink annotation because the signature field represents the designated signing area in the PDF form.

Programmatic Signature Example

A signature can also be represented programmatically using an ink annotation:

JavaScript
const formFieldName = "signature";

const formFields = await instance.getFormFields();

const field = formFields.find(
  (f) =>
    f.name === formFieldName &&
    f instanceof NutrientViewer.FormFields.SignatureFormField
);

const annotations = await instance.getAnnotations(0);

const widget = annotations.find(
  (a) =>
    a instanceof NutrientViewer.Annotations.WidgetAnnotation &&
    a.formFieldName === field.name
);

const annotation = new NutrientViewer.Annotations.InkAnnotation({
  pageIndex: 0,
  lines: NutrientViewer.Immutable.List([
    NutrientViewer.Immutable.List([
      new NutrientViewer.Geometry.DrawingPoint({
        x: widget.boundingBox.left + 5,
        y: widget.boundingBox.top + 5,
      }),
      new NutrientViewer.Geometry.DrawingPoint({
        x: widget.boundingBox.left + widget.boundingBox.width - 10,
        y: widget.boundingBox.top + widget.boundingBox.height - 10,
      }),
    ]),
  ]),
  boundingBox: widget.boundingBox,
  isSignature: true,
});

await instance.create(annotation);

Disabling PDF Forms

If the PDF should be displayed but users should not be allowed to fill in any form fields, forms can be disabled during viewer initialization:

JavaScript
NutrientViewer.load({
  container,
  document: "document.pdf",
  isEditableAnnotation: function (annotation) {
        if (!(annotation instanceof NutrientViewer.Annotations.WidgetAnnotation) ||
          (annotation instanceof PSPDFKit.Annotations.WidgetAnnotation &&
            readOnlyFields.includes(annotation.formFieldName)) ||
          signaturesForDisabling.includes(annotation.formFieldName)
        ) {
          return false;
        } else {
          return true;
        }
      },
});

This is useful for scenarios where the document should be view-only.


Word Templates Module

What Is a Word Template?

The Word Templates module allows a DOCX document to act as a template.

Instead of hardcoding values into the document, the DOCX contains placeholders:

Hello {{name}}

Your invoice amount is {{amount}}.

At runtime, the placeholders are replaced with values supplied through a JSON template model.

The output can then be retained as DOCX or converted to PDF.


Three Components of Word Templating

Word templating consists of three main components:

Component

Description

DOCX Template

The Word document containing placeholders

Template Model

JSON containing the values to populate

Delimiter Configuration

Defines the characters surrounding placeholders

For example:

DOCX Template
      +
JSON Model
      +
Delimiter Configuration
      ↓
Populated DOCX
      ↓
PDF

Template Model

The template model is a JSON object containing two sections:

JSON
{
  "config": {
    "delimiter": {
      "start": "{{",
      "end": "}}"
    }
  },
  "model": {
    "name": "Alex Smith",
    "text": "Hello World!",
    "amount": "$249.99"
  }
}

config

Defines how placeholders are identified.

"config": {
  "delimiter": {
    "start": "{{",
    "end": "}}"
  }
}

This means:

{{name}}
{{amount}}
{{customerName}}

are recognized as placeholders.

model

Contains the actual data.

"model": {
  "name": "Alex Smith",
  "text": "Hello World!",
  "amount": "$249.99"
}

The relationship is:

DOCX Placeholder          Model Key

{{name}}       ----------> name
{{text}}       ----------> text
{{amount}}     ----------> amount

Placeholder Naming Rules

Placeholder names should contain:

  • Letters: a-z, A-Z

  • Numbers: 0-9

  • Underscores: _

Valid

{{name}}
{{firstName}}
{{item_1}}
{{TOTAL_AMOUNT}}

Invalid

{{first-name}}
{{item.price}}
{{user@email}}
{{my placeholder}}

A good convention is to use descriptive camelCase or snake_case names.

For example:

{{customerName}}
{{applicationNumber}}
{{loanAmount}}
{{TOTAL_AMOUNT}}

Loading and Populating a Word Template

The Web SDK provides:

NutrientViewer.populateDocumentTemplate()

to populate a DOCX template.

Example:

JavaScript
const data = {
  config: {
    delimiter: {
      start: "{{",
      end: "}}",
    },
  },
  model: {
    name: "Alex Smith",
    text: "Hello World!",
    amount: "$249.99",
  },
};

const buffer = await NutrientViewer.populateDocumentTemplate(
  {
    document: "template.docx",
  },
  data
);

The important point here is that the template itself and the data model are separate.

template.docx
     +
data
     ↓
populateDocumentTemplate()
     ↓
ArrayBuffer
     ↓
Populated DOCX

Converting the Populated DOCX to PDF

Once the DOCX has been populated, the resulting ArrayBuffer can be converted to PDF.

JavaScript
const pdfBuffer = await NutrientViewer.convertToPDF(
  {
    document: buffer,
  },
  NutrientViewer.Conformance.PDFA_1A
);

The complete process is therefore:

             template.docx
                   +
              JSON Model
                   |
                   v
 populateDocumentTemplate()
                   |
                   v
        Populated DOCX
          (ArrayBuffer)
                   |
                   v
          convertToPDF()
                   |
                   v
             Final PDF