Use a JSON file with field values to programmatically populate and extract data from editable PDF forms.
This demo allows you to:
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.
Apryse collects some data regarding your usage of the SDK for product improvement.
The data that Apryse collects include:
For clarity, no other data is collected by the SDK and Apryse has no access to the contents of your documents.
If you wish to continue without data collection, contact us and we will email you a no-tracking trial key for you to get started.
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 DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales