Office Template Fill Showcase Demo Code Sample

Requirements
View Demo

Securely merge JSON data with MS Word, PowerPoint, and Excel templates directly in the browser - no server required. You can fill PDF templates using text or images.

This demo allows you to:

  • Load Office documents, such as Word, PowerPoint, and Excel
  • Recognize Office documents containing clauses denoted by double curly braces `{{FIELD_NAME}}`
  • Download the updated document as a PDF with your updated clauses

Implementation steps
To add Office Template Fill capability to WebViewer:

Step 1: Choose your preferred web stack for WebViewer
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: N/A, model: GPT-4, version: N/A, date: August 13, 2025
3// File: 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/letter_template.docx';
10
11// The list of registered panels in the main webviewer
12let viewerPanels = null;
13
14// The tab panel, representing the webviewer left panel
15const tabPanel = {
16 handle: null,
17 dataElement: 'tabPanel'
18};
19
20// The custom template sub-panel to be registered
21const templatePanel = {
22 handle: null,
23 dataElement: 'templatePanel',
24 templateKeys: null,
25 render: null,
26};
27
28// The logos to choose from
29const logos = [
30 { title: 'Angular', file: '/assets/angular.png' },
31 { title: 'React', file: '/assets/react.png' },
32 { title: 'Vue', file: '/assets/vue.png' },
33];
34
35// The rows to insert into the table
36let insertRows = [
37 ['Title', 'Web Developer'],
38 ['Start Date', '01/10/2022'],
39 ['Expected Salary', '$100,000'],
40];
41
42// The logo to insert into the document
43const logoJSON = {
44 val: { image_url: logos[0].file, width: 64, height: 64 },
45 type: 'image',
46};
47
48// The table to insert into the document
49const tableJSON = {
50 val: { insert_rows: insertRows },
51 type: 'table',
52};
53
54// A sample JSON data
55const sampleJSON = {
56 dest_street_address: { val: '187 Duizelstraat', type: 'text' },
57 applicant_first_name: { val: 'Janice', type: 'text' },
58 applicant_surname: { val: 'Symonds', type: 'text' },
59 Date: { val: '01/06/2022', type: 'text' },
60 sender_name: { val: 'Arnold Smith', type: 'text' },
61 company_name: { val: 'React-Faux', type: 'text' },
62 logo: logoJSON,
63 table: tableJSON,
64};
65
66// The initial selected logo. By default it is Angular
67let selectedButton = logos[0].title;
68
69WebViewer(
70 {
71 path: '/lib',
72 initialDoc: initialDoc,
73 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
74 enableMeasurement: true,
75 licenseKey: licenseKey, // Replace with your license key
76 },
77 document.getElementById('viewer')
78).then((instance) => {
79 const { documentViewer } = instance.Core;
80
81 // Customize the main webviewer left panel after the load completion.
82 // The left panel will display the template sub-panel with input fields.
83 // The input fields are representing the template keys were extracted from the loaded document.
84 documentViewer.addEventListener('documentLoaded', async () => {
85 const doc = documentViewer.getDocument();
86 templatePanel.templateKeys = await doc.getTemplateKeys();
87 customizeUI(instance);
88 });
89
90 console.log('✅ WebViewer loaded successfully.');
91}).catch((error) => {
92 console.error('❌ Failed to initialize WebViewer:', error);
93});
94
95// Customize the main webviewer left panel after the load completion
96const customizeUI = (instance) => {
97 const { UI } = instance;
98
99 // Close the tab panel (if it's open) for refreshment.
100 UI.closeElements([tabPanel.dataElement]);
101
102 // Get the list of registered panels in the main webviewer
103 viewerPanels = UI.getPanels();
104
105 // Find the Tab Panel to modify. The template sub-panel will be added to this Tab panel.
106 tabPanel.handle = viewerPanels.find((panel) => panel.dataElement === tabPanel.dataElement);
107
108 // Register the custom template sub-panel
109 RegisterTemplatePanel(instance);
110
111 // Add the new custom template sub-panel to list of sub-panels under the Tab Panel
112 templatePanel.handle = { render: templatePanel.dataElement };
113 tabPanel.handle.panelsList = [templatePanel.handle, ...tabPanel.handle.panelsList];
114
115 UI.openElements([tabPanel.dataElement]);
116 UI.setPanelWidth(tabPanel.dataElement, 400);
117};
118
119// Register the custom template sub-panel
120const RegisterTemplatePanel = (instance) => {
121 templatePanel.render = createTemplatePanelElements(instance);
122 instance.UI.addPanel({
123 dataElement: templatePanel.dataElement,
124 location: 'left',
125 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>',
126 title: 'Template',
127 render: () => templatePanel.render,
128 });
129};
130
131// Create the template panel elements.
132// The elements will be created dynamically,
133// based on the template keys extracted from
134// the loaded document.
135const createTemplatePanelElements = (instance) => {
136 let panelDiv = document.createElement('div');
137 panelDiv.id = 'template';
138 let paragraph = document.createTextNode('Securely merge JSON data with MS Word, PowerPoint and Excel templates directly in the browser - no server required! Easily generate documents using text/image templates with JavaScript.');
139 panelDiv.appendChild(paragraph);
140
141 let dividerDiv = document.createElement('div');
142 dividerDiv.style.borderTop = '1px solid #ccc';
143 dividerDiv.style.margin = '10px 0';
144 panelDiv.appendChild(dividerDiv);
145
146 if (templatePanel.templateKeys.length === 0) {
147 panelDiv.appendChild(document.createTextNode('Sorry, there are no template keys in this document.'));
148 return panelDiv;
149 }
150
151 let labelField = null;
152 let inputField = null;
153 let logoField = null;
154 let tableField = null;
155
156 templatePanel.templateKeys.forEach(element => {
157 switch (element) {
158 // if logo key is found, then create its div.
159 case 'logo':
160 logoField = document.createElement('div');
161 logoField.appendChild(document.createTextNode(`${element}:`));
162 logoField.appendChild(document.createElement('p'));
163 logoField.id = 'logo';
164 if (sampleJSON[element].type === 'image') {
165 logos.forEach(image => {
166 const img = document.createElement('img');
167 img.src = image.file;
168 img.alt = image.title;
169 img.style.width = '20px';
170 img.style.height = '20px';
171
172 let frameworkBtn = document.createElement('button');
173 frameworkBtn.title = image.title;
174 frameworkBtn.id = image.title;
175 frameworkBtn.style.marginRight = '5px';
176 if (image.title === logos[0].title)
177 frameworkBtn.style.backgroundColor = 'blue';
178 else
179 frameworkBtn.style.backgroundColor = 'white';
180 frameworkBtn.appendChild(img);
181 frameworkBtn.onclick = () => {
182 selectedButton = image.title;
183 frameworkBtn.style.backgroundColor = 'blue';
184 templatePanel.render.querySelectorAll('button').forEach(btn => {
185 if (btn !== frameworkBtn)
186 btn.style.backgroundColor = 'white';
187 });
188 }
189 logoField.appendChild(frameworkBtn);
190 });
191
192 let heightInput = document.createElement('input');
193 heightInput.id = 'height';
194 heightInput.title = 'H (pts)';
195 heightInput.type = typeof (sampleJSON[element].val.height);
196 heightInput.value = sampleJSON[element].val.height;
197 heightInput.style.height = '40px';
198 heightInput.style.width = '40px';
199 heightInput.disabled = true;
200 heightInput.style.marginRight = '5px';
201 logoField.appendChild(heightInput);
202
203 let widthInput = document.createElement('input');
204 widthInput.id = 'width';
205 widthInput.title = 'W (pts)';
206 widthInput.type = typeof (sampleJSON[element].val.width);
207 widthInput.value = sampleJSON[element].val.width;
208 widthInput.style.height = '40px';
209 widthInput.style.width = '40px';
210 widthInput.disabled = true;
211 widthInput.style.marginRight = '5px';
212 logoField.appendChild(widthInput);
213 }
214 break;
215 // if table key is found, then create its div.
216 case 'table':
217 tableField = document.createElement('div');
218 tableField.appendChild(document.createTextNode(`${element}:`));
219 tableField.appendChild(document.createElement('p'));
220 tableField.id = 'table';
221 sampleJSON[element].val.insert_rows.forEach((row) => {
222 let rowLabel = document.createTextNode(`${row[0]}:`);
223 let rowInput = document.createElement('input');
224 rowInput.id = `${row[0]}`;
225 rowInput.type = 'text';
226 rowInput.value = row[1];
227 tableField.appendChild(document.createElement('p'));
228 tableField.appendChild(rowLabel);
229 tableField.appendChild(document.createElement('p'));
230 tableField.appendChild(rowInput);
231 });
232 break;
233 // input fields for text
234 default:
235 labelField = document.createTextNode(`${element}:`);
236 inputField = document.createElement('input');
237 inputField.id = element;
238 if (sampleJSON[element] === undefined) {
239 inputField.type = 'text';
240 inputField.placeholder = 'Text To Fill Template';
241 }
242 else {
243 inputField.type = sampleJSON[element].type;
244 inputField.value = sampleJSON[element].val;
245 }
246 }
247
248 panelDiv.appendChild(labelField);
249 panelDiv.appendChild(document.createElement('p'));
250
251 if (inputField)
252 panelDiv.appendChild(inputField);
253
254 panelDiv.appendChild(document.createElement('p'));
255 });
256
257 if (logoField) {
258 panelDiv.appendChild(dividerDiv.cloneNode());
259 panelDiv.appendChild(logoField);
260 }
261
262 if (tableField) {
263 panelDiv.appendChild(dividerDiv.cloneNode());
264 panelDiv.appendChild(tableField);
265 }
266
267 // Fill template and Reset document buttons
268 let fillTemplateButton = document.createElement('button');
269 fillTemplateButton.textContent = 'Fill Template';
270 fillTemplateButton.style.backgroundColor = 'white';
271 fillTemplateButton.style.color = 'blue';
272 fillTemplateButton.onclick = () => fillTemplate(instance); // Fill the template with data
273
274 let resetButton = document.createElement('button');
275 resetButton.textContent = 'Reset Document';
276 resetButton.style.backgroundColor = 'white';
277 resetButton.style.color = 'blue';
278 resetButton.onclick = () => instance.UI.loadDocument(initialDoc);
279
280 panelDiv.appendChild(dividerDiv.cloneNode());
281 panelDiv.appendChild(fillTemplateButton);
282 panelDiv.appendChild(document.createElement('p'));
283 panelDiv.appendChild(resetButton);
284
285 return panelDiv;
286};
287
288// Collect data from the custom template sub-panel fields,
289// then format it for the template
290// and apply it to the loaded document.
291const fillTemplate = async (instance) => {
292
293 const formattedValues = {};
294
295 // Collect data from input fields
296 templatePanel.render.querySelectorAll('input').forEach((input) => {
297 if (templatePanel.templateKeys.includes(input.id)) {
298 formattedValues[input.id] = input.value;
299 }
300 });
301
302 // Collect data from table fields
303 let tableInputs = templatePanel.render.querySelectorAll('#table input');
304 if (tableInputs.length > 0) {
305 insertRows = [];
306 tableInputs.forEach((input) =>
307 insertRows.push([input.id, input.value])
308 );
309
310 sampleJSON.table.val.insert_rows = insertRows;
311
312 formattedValues['table'] = sampleJSON.table.val;
313 }
314
315 // Collect data from logo field
316 sampleJSON['logo'].val.image_url = window.location.origin + logos.find(img => img.title === selectedButton).file;
317 formattedValues['logo'] = sampleJSON['logo'].val;
318
319 // Apply the formatted template values
320 await instance.Core.documentViewer.getDocument().applyTemplateValues(formattedValues);
321};

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales