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