Overview
Our application uses the Nutrient Web SDK for two primary document-processing capabilities:
-
PDF Forms — Load PDFs, access form fields and widget annotations, update field properties, and support electronic signatures.
-
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.
<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():
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
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:
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:
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:
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:
{
"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:
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.
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