Article summary
In my current project, our client wants to dynamically generate forms for their customers. On top of that, they want to be able to send the generated forms over to DocuSign automatically, so that customers can view and sign them digitally. This need requires a complex setup linking a database, a data provider, PDF-Lib, and DocuSign SDK. Recently, we figured out how to map form fields directly to DocuSign SDK. Here’s how we are using the two tools.
PDF-Lib – We are utilizing functions from PDF-Lib to help read in the prepared PDF Form and collect all possible form fields that were manually added to the PDFs.
DocuSign SDK – We are utilizing the SDK to authenticate, create envelopes, and generate signing URLs.
Given our project needs and the complexity of the generated forms, we chose to custom map the form fields instead of having DocuSign auto detect form fields. Since the form fields can vary in location on any given PDF, the trickiest part is to figure out the coordinates of these fields dynamically.
Using PDF-Lib
Here’s an example of how we use PDF-Lib to help us get and store the fields.
import {PDFDocument} from "pdf-lib"
//Read in the file
//PDFDocument.load takes string, Uint8Array, or ArrayBuffer
const uint8ArrayFile = fs.readFileSync('sample.pdf')
const pdf = PDFDocument.load(uint8ArrayFile)
//Store all Form Fields
const fields = pdf.getForm().getFields();
After storing all form fields, we want to traverse through these to obtain their coordinates on the page. The actual data regarding a form field lives in the widgets of a field. Given any field, a getWidgets
call will return an array of widgets
.
The reasoning is that in a PDF structure, a particular field can be related to other fields. For example, there can be multiple signature fields on one Form, and they are all labeled the same under signature
. In this case of duplicate signature
fields, getWidgets
returns an array containing all the signature
fields on this PDF. If we are not handling any duplicate fields, it is safe to take the very first widget
in the returned array.
const newFields = fields.map(field => {
//Actual data about the Field is in widgets
const widgets = field.acroField.getWidgets();
const currentWidget = widgets[0].
})
Mapping
Now that we have access to the coordinates, the mapping is fairly easy to match DocuSign structures for tabs
. One thing to note is that PDF-Lib’s coordinates are based with (0,0) at the bottom left of any given page, and DocuSign’s coordinates are based with (0,0) at the top left of the page. It’s nothing a little math can’t deal with. In order to calculate the offsets, we need to know attributes about the page the field is on. PDF-Lib has a function called findPageForAnnotationRef
that will return the page the current field is in.
(field) => {
...
const fieldRef = field.ref;
const page = field.doc.findPageForAnnotationRef(fieldRef);
const coord = {
x: currentWidget.getRectangle().x;
y: page.getHeight() - w.getRectangle().y - w.getRectangle().height
}
return {
name,
coord
}
}
Now that the coordinates are mapped correctly from PDF-Lib fields to DocuSign coordinates, they can be passed along to DocuSign SDK to create envelopes. What is not captured here is that DocuSign splits their tabs into multiple types, signTabs
, dateTabs
,initialTabs
, and more. There are several ways to capture form field tabs, and one way is to use the name of when the form fields are added into the form. A consistent naming convention, such as naming all signTabs
as signature
, helps to map tabs and fields correctly.
From here, DocuSign SDK offers good documentation on how to construct envelopes and tabs.