PDF Forms Showcase Demo Code Sample

Requirements
View Demo

Use a JSON file with field values to programmatically populate and extract data from editable PDF forms.

This demo allows you to:

  • Upload your own PDF form
  • Use a sample JSON to input data into the sample PDF form
  • Add annotations and customize contents
  • Edit the layout

Implementation steps

To add PDF Form capability with WebViewer:

Step 1: Get started with WebViewer in your preferred web stack
Step 2: Add the ES6 JavaScript sample code provided in this guide

Once you generate your license key, it will automatically be included in your sample code below.

License Key

1// ES6 Compliant Syntax
2// Copilot name: GitHub Copilot, version: 1.0.0, model: GPT-4, version: 2024-06, date: 2025-09-18
3// File: pdf-form/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
8
9const initialDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/form-1040.pdf';
10
11// Sample JSON data to fill the form-1040.pdf
12const sampleJSONData = {
13 "topmostSubform[0].Page1[0].f1_04[0]": "Jennifer F.",
14 "topmostSubform[0].Page1[0].f1_05[0]": "Ng",
15 "topmostSubform[0].Page1[0].f1_06[0]": "0100101",
16 "topmostSubform[0].Page1[0].f1_07[0]": "Dana",
17 "topmostSubform[0].Page1[0].f1_08[0]": "York",
18 "topmostSubform[0].Page1[0].SpouseSSN[0]": "1111111",
19 "topmostSubform[0].Page1[0].SpouseSSN[0].f1_09[0]": "1111111",
20 "topmostSubform[0].Page1[0].Address[0]": "hi",
21 "topmostSubform[0].Page1[0].Address[0].f1-10[0]": "123 Broadway Avenue",
22 "topmostSubform[0].Page1[0].Address[0].f1-11[0]": "24",
23 "topmostSubform[0].Page1[0].Address[0].f1-12[0]": "Los Angeles, California, 90210",
24 "topmostSubform[0].Page1[0].Address[0].f1_13[0]": "Canada",
25 "topmostSubform[0].Page1[0].Address[0].f1_14[0]": "British Columbia",
26 "topmostSubform[0].Page1[0].Address[0].f1_15[0]": "V6T1PX",
27 "topmostSubform[0].Page1[0].c1_01[0]": "1",
28 "topmostSubform[0].Page1[0].c1_02[0]": "Off",
29 "topmostSubform[0].Page1[0].Lines1-3[0]": "2",
30 "topmostSubform[0].Page1[0].Lines1-3[0].c1_03[0]": "Off",
31 "topmostSubform[0].Page1[0].Lines1-3[0].c1_03[1]": "Off",
32 "topmostSubform[0].Page1[0].Lines1-3[0].c1_03[2]": "Off",
33 "topmostSubform[0].Page1[0].Lines1-3[0].f1-16[0]": "Off"
34};
35
36// Temporary JSON data to hold the live form data
37let tempJSONData = {};
38
39// The list of registered panels in the main webviewer
40let viewerPanels = null;
41
42// The tab panel, representing the webviewer left panel
43const tabPanel = {
44 handle: null,
45 dataElement: 'tabPanel'
46};
47
48// The custom form sub-panel to be registered
49const formPanel = {
50 handle: null,
51 dataElement: 'formPanel',
52 render: null,
53};
54
55WebViewer(
56 {
57 path: '/lib',
58 fullAPI: true, // This is required to download the pdf with flattening contents.
59 initialDoc: initialDoc,
60 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
61 enableMeasurement: true,
62 licenseKey: licenseKey, // Replace with your license key
63 },
64 document.getElementById('viewer')
65).then((instance) => {
66 const { documentViewer, annotationManager } = instance.Core;
67
68 // Customize the main webviewer left panel after the load completion.
69 // The left panel will display the template sub-panel with fields.
70 documentViewer.addEventListener('documentLoaded', () => {
71 customizeUI(instance);
72
73 // Reset the temporary JSON data when a new document is loaded
74 tempJSONData = {};
75 });
76
77 // Capture form field changes and update the temporary JSON data
78 annotationManager.addEventListener('fieldChanged', (field, value) => {
79 if (tempJSONData[field.name])
80 tempJSONData[field.name] = value;
81 else
82 tempJSONData = { ...tempJSONData, [field.name]: value };
83
84 // Update the scroll box text content with the latest JSON data
85 const scrollBoxText = formPanel.render.querySelector("#scrollBox").querySelector("pre");
86 scrollBoxText.textContent = JSON.stringify(tempJSONData, null, 2);
87
88 // Enable the "Open in Dialog" button
89 enableButton(formPanel.render.querySelector("#jsonDataDialogButton"), true);
90 });
91
92 console.log('✅ WebViewer loaded successfully.');
93}).catch((error) => {
94 console.error('❌ Failed to initialize WebViewer:', error);
95});
96
97// Customize the main webviewer left panel after the load completion
98const customizeUI = (instance) => {
99 const { UI } = instance;
100
101 // Close the tab panel (if it's open) for refreshment.
102 UI.closeElements([tabPanel.dataElement]);
103
104 // Get the list of registered panels in the main webviewer
105 viewerPanels = UI.getPanels();
106
107 // Find the Tab Panel to modify. The template sub-panel will be added to this Tab panel.
108 tabPanel.handle = viewerPanels.find((panel) => panel.dataElement === tabPanel.dataElement);
109
110 // Register the custom form sub-panel
111 RegisterFormPanel(instance);
112
113 // Add the new custom form sub-panel to list of sub-panels under the Tab Panel
114 formPanel.handle = { render: formPanel.dataElement };
115 tabPanel.handle.panelsList = [formPanel.handle, ...tabPanel.handle.panelsList];
116
117 UI.openElements([tabPanel.dataElement]);
118 UI.setPanelWidth(tabPanel.dataElement, 400);
119};
120
121// Register the custom form sub-panel
122const RegisterFormPanel = (instance) => {
123 formPanel.render = createFormPanelElements(instance);
124 instance.UI.addPanel({
125 dataElement: formPanel.dataElement,
126 location: 'left',
127 icon: '<svg width="18px" height="18px" viewBox="0 0 24 24" id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#080808;}</style></defs><title>template</title><path class="cls-1" d="M21,.5H3a2,2,0,0,0-2,2V22a2,2,0,0,0,2,2H21a2,2,0,0,0,2-2V2.5A2,2,0,0,0,21,.5Zm0,2v2H3v-2ZM3,22V6.5H21V22Z"/><path class="cls-1" d="M12.5,4H20a.5.5,0,0,0,0-1H12.5a.5.5,0,0,0,0,1Z"/><path class="cls-1" d="M4.5,4a.43.43,0,0,0,.19,0,.35.35,0,0,0,.16-.11A.47.47,0,0,0,5,3.5a.43.43,0,0,0,0-.19.36.36,0,0,0-.11-.16.5.5,0,0,0-.7,0A.35.35,0,0,0,4,3.31.43.43,0,0,0,4,3.5a.51.51,0,0,0,.5.5Z"/><path class="cls-1" d="M5.65,3.85A.36.36,0,0,0,5.81,4,.44.44,0,0,0,6,4a.47.47,0,0,0,.35-.15.36.36,0,0,0,.11-.16.6.6,0,0,0,0-.19.51.51,0,0,0-.15-.35A.49.49,0,0,0,5.81,3a.36.36,0,0,0-.16.11.47.47,0,0,0-.15.35.4.4,0,0,0,0,.19A.35.35,0,0,0,5.65,3.85Z"/><path class="cls-1" d="M8,8H4.5a1,1,0,0,0,0,2H8A1,1,0,0,0,8,8Z"/><path class="cls-1" d="M8,11.67H4.5a1,1,0,0,0,0,2H8a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M8,15.33H4.5a1,1,0,0,0,0,2H8a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M8,19H4.5a1,1,0,0,0,0,2H8a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,8H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,11.67H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,15.33H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,19H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,8h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,11.67h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,15.33h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,19h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/></svg>',
128 title: 'Form',
129 render: () => formPanel.render,
130 });
131};
132
133// Create the form panel elements.
134// The elements will be created dynamically
135// when the form panel is registered after
136// loading the document.
137const createFormPanelElements = (instance) => {
138 let panelDiv = document.createElement('div');
139 panelDiv.id = 'form';
140 let paragraph = document.createTextNode('A demo of the PDF form capabilities in WebViewer, a JavaScript-based PDF SDK for web apps. Programmatically fill and extract data from forms with JavaScript.');
141 panelDiv.appendChild(paragraph);
142
143 let dividerDiv = document.createElement('div');
144 dividerDiv.style.borderTop = '1px solid #ccc';
145 dividerDiv.style.margin = '10px 0';
146 panelDiv.appendChild(dividerDiv);
147
148 // Format and insert JSON data
149 const scrollBox = document.createElement("div");
150 scrollBox.id = 'scrollBox';
151 scrollBox.style.width = "350px";
152 scrollBox.style.height = "350px";
153 scrollBox.style.border = "2px solid #444";
154 scrollBox.style.overflow = "scroll"; // Enables both vertical and horizontal scroll
155 scrollBox.style.whiteSpace = "nowrap"; // Prevents wrapping for horizontal scroll
156 scrollBox.style.padding = "10px";
157 scrollBox.style.fontFamily = "monospace";
158 scrollBox.style.backgroundColor = "black";
159 scrollBox.style.color = "white";
160
161 const jsonContent = document.createElement("pre");
162 jsonContent.textContent = 'Change a form field to see live data!';
163 scrollBox.appendChild(jsonContent);
164 panelDiv.appendChild(scrollBox);
165
166 // Open JSON data dialog button
167 let jsonDataDialogButton = document.createElement('button');
168 jsonDataDialogButton.textContent = 'Open in Dialog';
169 jsonDataDialogButton.id = 'jsonDataDialogButton';
170 jsonDataDialogButton.onclick = () => openJsonDataDialog(JSON.stringify(tempJSONData, null, 2));
171 panelDiv.appendChild(jsonDataDialogButton);
172 panelDiv.appendChild(document.createElement('p'));
173 enableButton(jsonDataDialogButton, false);
174
175 // If the loaded document is form-1040.pdf, show the Sample JSON Data button
176 const doc = instance.Core.documentViewer.getDocument();
177 if (doc && doc.filename === 'form-1040.pdf') {
178 panelDiv.appendChild(dividerDiv.cloneNode());
179 paragraph = document.createTextNode('Alternatively, fill your form with pre-existing JSON data.');
180 panelDiv.appendChild(paragraph);
181
182 // Sample JSON Data button
183 let sampleJSONDataButton = document.createElement('button');
184 sampleJSONDataButton.textContent = 'Sample JSON Data';
185 sampleJSONDataButton.style.backgroundColor = 'blue';
186 sampleJSONDataButton.style.color = 'white';
187 sampleJSONDataButton.onclick = () => fillJsonDataDialog(instance);
188 panelDiv.appendChild(sampleJSONDataButton);
189 }
190
191 // Downloading with flattening button
192 let downloadingFlatteningButton = document.createElement('button');
193 downloadingFlatteningButton.textContent = 'Downloading with flattening';
194 downloadingFlatteningButton.style.backgroundColor = 'blue';
195 downloadingFlatteningButton.style.color = 'white';
196 downloadingFlatteningButton.onclick = () => instance.UI.downloadPdf({
197 flags: instance.Core.SaveOptions.LINEARIZED,
198 includeAnnotations: true,
199 flatten: true
200 });
201
202 // Download only button
203 let downloadButton = document.createElement('button');
204 downloadButton.textContent = 'Download only';
205 downloadButton.style.backgroundColor = 'blue';
206 downloadButton.style.color = 'white';
207 downloadButton.onclick = () => instance.UI.downloadPdf({
208 flags: instance.Core.SaveOptions.LINEARIZED,
209 includeAnnotations: true
210 });
211
212 panelDiv.appendChild(dividerDiv.cloneNode());
213 paragraph = document.createElement('p');
214
215 // Create a text node for the first part of the text
216 const textBeforeLink = document.createTextNode('Optionally apply ');
217
218 // Create an anchor (link) element
219 const link = document.createElement('a');
220 link.href = 'https://docs.apryse.com/web/guides/annotation/flatten-annotations';
221 link.textContent = 'flattening';
222 link.target = '_blank'; // Opens the link in a new tab
223
224 // Create a text node for the text after the link
225 const textAfterLink = document.createTextNode(' to permanently add field data.');
226
227 // Append the text and link to the paragraph
228 paragraph.appendChild(textBeforeLink);
229 paragraph.appendChild(link);
230 paragraph.appendChild(textAfterLink);
231 panelDiv.appendChild(paragraph);
232
233 panelDiv.appendChild(document.createElement('p'));
234 panelDiv.appendChild(downloadingFlatteningButton);
235 panelDiv.appendChild(document.createElement('p'));
236 panelDiv.appendChild(downloadButton);
237
238 return panelDiv;
239};
240
241// Enable or disable a button based on the state
242const enableButton = (button, state) => {
243 button.disabled = !state;
244 button.style.backgroundColor = (state) ? 'blue' : 'gray';
245 button.style.color = (state) ? 'white' : 'darkgray';
246};
247
248// Open JSON data in a dialog box with zoom in/out and close buttons
249const openJsonDataDialog = (jsonText) => {
250 let fontSize = 14;
251
252 // Create overlay
253 const overlay = document.createElement("div");
254 overlay.className = "modal-overlay";
255 overlay.onclick = (e) => {
256 if (e.target === overlay) {
257 document.body.removeChild(overlay);
258 }
259 };
260
261 // Modal box
262 const modal = document.createElement("div");
263 modal.className = "modal-box";
264
265 // Controls
266 const controls = document.createElement("div");
267 controls.className = "modal-controls";
268
269 const title = document.createElement("span");
270 title.textContent = "Form Data";
271 title.style.fontWeight = "bold";
272 title.style.marginRight = "auto";
273 controls.appendChild(title);
274
275 const zoomInBtn = document.createElement("button");
276 zoomInBtn.textContent = "+";
277 zoomInBtn.onclick = () => {
278 fontSize += 2;
279 content.style.fontSize = fontSize + "px";
280 };
281
282 const zoomOutBtn = document.createElement("button");
283 zoomOutBtn.textContent = "-";
284 zoomOutBtn.onclick = () => {
285 fontSize = Math.max(10, fontSize - 2);
286 content.style.fontSize = fontSize + "px";
287 };
288
289 const closeBtn = document.createElement("button");
290 closeBtn.textContent = "Close";
291 closeBtn.className = "modal-close";
292 closeBtn.onclick = () => {
293 document.body.removeChild(overlay);
294 };
295
296 controls.appendChild(zoomInBtn);
297 controls.appendChild(zoomOutBtn);
298 controls.appendChild(closeBtn);
299
300 // Content
301 const content = document.createElement("pre");
302 content.className = "modal-content";
303 content.style.fontSize = fontSize + "px";
304 content.innerHTML = jsonText;
305
306 modal.appendChild(controls);
307 modal.appendChild(content);
308 overlay.appendChild(modal);
309 document.body.appendChild(overlay);
310}
311
312// Fill the form with sample JSON data in a dialog box with Fill and Close buttons
313const fillJsonDataDialog = (instance) => {
314
315 // Create overlay
316 const overlay = document.createElement("div");
317 overlay.className = "modal-overlay";
318 overlay.onclick = (e) => {
319 if (e.target === overlay) {
320 document.body.removeChild(overlay);
321 }
322 };
323
324 // Modal box
325 const modal = document.createElement("div");
326 modal.className = "modal-box";
327
328 // Controls
329 const controls = document.createElement("div");
330 controls.className = "modal-controls";
331
332 const title = document.createElement("span");
333 title.textContent = "Sample JSON Data";
334 title.style.fontWeight = "bold";
335 title.style.marginRight = "auto";
336 controls.appendChild(title);
337
338 const fillBtn = document.createElement("button");
339 fillBtn.textContent = "Fill";
340 fillBtn.style.marginLeft = "475px";
341 fillBtn.onclick = () => {
342 const fieldManager = instance.Core.annotationManager.getFieldManager();
343 Object.keys(sampleJSONData).forEach((key) => {
344 const field = fieldManager.getField(key);
345 field && field.setValue(sampleJSONData[key]);
346 });
347 document.body.removeChild(overlay);
348 };
349
350 const closeBtn = document.createElement("button");
351 closeBtn.textContent = "Close";
352 closeBtn.className = "modal-close";
353 closeBtn.onclick = () => {
354 document.body.removeChild(overlay);
355 };
356
357 controls.appendChild(fillBtn);
358 controls.appendChild(closeBtn);
359
360 // Content
361 const content = document.createElement("pre");
362 content.className = "modal-content";
363 content.style.fontSize = "14px";
364 content.innerHTML = JSON.stringify(sampleJSONData, null, 2);
365
366 modal.appendChild(controls);
367 modal.appendChild(content);
368 overlay.appendChild(modal);
369 document.body.appendChild(overlay);
370}

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales