PDF Forms Showcase Demo Code Sample

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: Choose your preferred web stack
Step 2: Download required modules listed in the Demo Dependencies section below
Step 3: Add the ES6 JavaScript sample code provided in this guide

Demo Dependencies
This sample uses the following:

Want to see a live version of this demo?

Try the PDF Forms demo

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 Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales