PDF Search Showcase Demo Code Sample

Add PDF search functionality with configurable search methods that allow users to refine or broaden string match criteria.

This demo allows you to:

  • Choose your own PDF file
  • Type a search string
  • Configure the search options:
    • Case Sensitive
    • Whole Word
    • Wild Card
    • Regular Expression
  • Highlight matching string in the document

Implementation steps
To add PDF Search 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 Search demo

1// ES6 Compliant Syntax
2// Copilot name: GitHub Copilot, version: 1.0.0, model: GPT-4, version: 2024-06, date: 2025-09-23
3// File: pdf-search/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7WebViewer(
8 {
9 path: '/lib',
10 initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/section-508.pdf',
11 enableFilePicker: true, // Enable file picker to open files in WebViewer -> menu icon -> Open File.
12 enableMeasurement: true,
13 licenseKey: 'YOUR_LICENSE_KEY', // Replace with your license key.
14 },
15 document.getElementById('viewer')
16).then((instance) => {
17
18 // Customize the main webviewer left panel after the load completion.
19 instance.Core.documentViewer.addEventListener('documentLoaded', () => {
20 customizeUI(instance);
21 });
22
23 // Define the search listener to capture search results.
24 // This listener is called when search results are returned.
25 const searchListener = (searchPattern, options, results) => {
26
27 // Update the checkbox states on the custom search panel based on the search options used.
28 checkBoxControl(checkBoxData[0]).checked = options.caseSensitive;
29 checkBoxControl(checkBoxData[1]).checked = options.wholeWord;
30
31 searchResults = formattedSearchResults(results);
32 updateResultsViewer();
33 };
34
35 // Add search listener to capture search results.
36 instance.UI.addSearchListener(searchListener);
37
38 console.log('✅ WebViewer loaded successfully.');
39}).catch((error) => {
40 console.error('❌ Failed to initialize WebViewer:', error);
41});
42
43// Store the search results.
44let searchResults = null;
45
46// The list of registered panels in the main webviewer.
47let viewerPanels = null;
48
49// The tab panel, representing the webviewer left panel.
50const tabPanel = {
51 handle: null,
52 dataElement: 'tabPanel'
53};
54
55// The custom search sub-panel to be registered.
56const leftSearchPanel = {
57 handle: null,
58 dataElement: 'leftSearchPanel',
59 render: null,
60};
61
62const checkBoxData = [
63 { label: "Case sensitive" },
64 { label: "Whole word" },
65 { label: "Wild card" },
66 { label: "Regular expression" }
67];
68
69// Customize the main webviewer left panel.
70const customizeUI = (instance) => {
71 const { UI } = instance;
72
73 // Close the tab panel (if it's open) for refreshment.
74 UI.closeElements([tabPanel.dataElement]);
75
76 // Get the list of registered panels in the main webviewer.
77 viewerPanels = UI.getPanels();
78
79 // Find the Tab Panel to modify. The search sub-panel will be added to this Tab panel.
80 tabPanel.handle = viewerPanels.find((panel) => panel.dataElement === tabPanel.dataElement);
81
82 // Register the custom search sub-panel.
83 RegisterSearchPanel(instance);
84
85 // Add the new custom search sub-panel to list of sub-panels under the Tab Panel.
86 leftSearchPanel.handle = { render: leftSearchPanel.dataElement };
87 tabPanel.handle.panelsList = [leftSearchPanel.handle, ...tabPanel.handle.panelsList];
88
89 UI.openElements([tabPanel.dataElement]);
90 UI.setPanelWidth(tabPanel.dataElement, 400);
91};
92
93// Register the custom search sub-panel.
94const RegisterSearchPanel = (instance) => {
95 leftSearchPanel.render = createSearchPanelElements(instance);
96 instance.UI.addPanel({
97 dataElement: leftSearchPanel.dataElement,
98 location: 'left',
99 icon: '<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="18" height="18" viewBox="0 0 24 24"><path d="M 10 2 C 5.5935644 2 2 5.5935677 2 10 C 2 14.406432 5.5935644 18 10 18 C 11.844022 18 13.540969 17.365427 14.896484 16.310547 L 20.292969 21.707031 A 1.0001 1.0001 0 1 0 21.707031 20.292969 L 16.310547 14.896484 C 17.365427 13.540969 18 11.844021 18 10 C 18 5.5935677 14.406436 2 10 2 z M 10 4 C 13.325556 4 16 6.674446 16 10 C 16 13.325554 13.325556 16 10 16 C 6.6744439 16 4 13.325554 4 10 C 4 6.674446 6.6744439 4 10 4 z"></path></svg>',
100 title: 'Search',
101 render: () => leftSearchPanel.render,
102 });
103};
104
105// Create the search panel elements.
106const createSearchPanelElements = (instance) => {
107 let panelDiv = document.createElement('div');
108 panelDiv.id = 'search';
109 let paragraph = document.createTextNode('A demo of the PDF search and indexing capabilities in WebViewer, a JavaScript-based PDF SDK for web apps. Found words are highlighted throughout the PDF document.');
110 panelDiv.appendChild(paragraph);
111
112 let dividerDiv = document.createElement('div');
113 dividerDiv.style.borderTop = '1px solid #ccc';
114 dividerDiv.style.margin = '10px 0';
115 panelDiv.appendChild(dividerDiv);
116
117 // Search input field.
118 const inputSearch = document.createElement('input');
119 inputSearch.id = 'inputSearch';
120 inputSearch.type = 'text';
121 inputSearch.placeholder = 'Search here ...';
122 inputSearch.addEventListener("input", () => {
123 enableButton(searchButton, inputSearch.value.trim() !== '');
124 });
125 inputSearch.addEventListener("keydown", function (event) {
126 if (event.key === "Enter") {
127 searchPDF(instance);
128 }
129 });
130 panelDiv.appendChild(inputSearch);
131
132 // Search button
133 const searchButton = document.createElement('button');
134 searchButton.textContent = 'Search';
135 enableButton(searchButton, false); // Initially disable the search button.
136 searchButton.style.marginLeft = '10px';
137 searchButton.onclick = () => searchPDF(instance); // Search the PDF document.
138 panelDiv.appendChild(searchButton);
139
140 // Create checkboxes for items in the checkBoxData array.
141 checkBoxData.forEach(item => {
142
143 // Checkbox input
144 const checkbox = document.createElement('input');
145 checkbox.type = 'checkbox';
146 checkbox.id = labelToId(item.label);
147
148 // Checkbox label
149 const label = document.createElement('label');
150 label.textContent = item.label;
151
152 panelDiv.appendChild(document.createElement('p'));
153 panelDiv.appendChild(checkbox);
154 panelDiv.appendChild(label);
155 });
156
157 // Display search results.
158 let jsonDiv = document.createElement('div');
159 jsonDiv.id = 'json';
160 const jsonTitle = document.createElement("h3");
161 jsonTitle.textContent = "Search Results";
162 jsonDiv.appendChild(jsonTitle);
163 jsonDiv.appendChild(document.createElement('p'));
164
165 const span = document.createElement("span");
166 span.id = 'resultsText';
167 let resultsText = document.createTextNode('');
168 span.appendChild(resultsText);
169 jsonDiv.appendChild(span);
170 jsonDiv.appendChild(document.createElement('p'));
171
172 const scrollBox = document.createElement("div");
173 scrollBox.style.width = "350px";
174 scrollBox.style.height = "350px";
175 scrollBox.style.border = "2px solid #444";
176 scrollBox.style.overflow = "scroll"; // Enables both vertical and horizontal scroll.
177 scrollBox.style.whiteSpace = "nowrap"; // Prevents wrapping for horizontal scroll.
178 scrollBox.style.padding = "10px";
179 scrollBox.style.fontFamily = "monospace";
180 scrollBox.style.backgroundColor = "black";
181 scrollBox.style.color = "white";
182
183 // Format and insert results JSON data.
184 const jsonContent = document.createElement("pre");
185 scrollBox.appendChild(jsonContent);
186 jsonDiv.appendChild(scrollBox);
187
188 // Open JSON data dialog button.
189 let jsonDataDialogButton = document.createElement('button');
190 jsonDataDialogButton.textContent = 'Open in Dialog';
191 jsonDataDialogButton.id = 'jsonDataDialogButton';
192 enableButton(jsonDataDialogButton, false); // Initially disable the button
193 jsonDataDialogButton.onclick = () => openJsonDataDialog();
194 jsonDiv.appendChild(jsonDataDialogButton);
195 jsonDiv.appendChild(document.createElement('p'));
196
197 panelDiv.appendChild(dividerDiv.cloneNode());
198 panelDiv.appendChild(jsonDiv);
199
200 return panelDiv;
201};
202
203// Format the search results for better readability.
204const formattedSearchResults = (results) => {
205 if (results === null || results.length === 0) {
206 return [];
207 }
208
209 const formatted = [];
210 results.forEach((result) => {
211 const element = {
212 ambient_str: result.ambient_str,
213 result_str: result.result_str,
214 result_str_start: result.result_str_start,
215 result_str_end: result.result_str_end,
216 page_num: result.page_num,
217 resultCode: result.resultCode,
218 quads: result.quads,
219 };
220
221 formatted.push(element);
222 });
223
224 return formatted;
225};
226
227// Update the results viewer with the latest search results.
228const updateResultsViewer = () => {
229 const resultsText = leftSearchPanel.render.querySelector('#resultsText');
230 resultsText.textContent = (searchResults && searchResults.length > 0) ? `${searchResults.length} results found. (Data has been formatted for readability)` : 'No results found';
231
232 const jsonContent = leftSearchPanel.render.querySelector('pre');
233 jsonContent.textContent = (searchResults && searchResults.length > 0) ? JSON.stringify(searchResults, null, 1) : '';
234
235 const jsonDataDialogButton = leftSearchPanel.render.querySelector('#jsonDataDialogButton');
236 enableButton(jsonDataDialogButton, (searchResults && searchResults.length > 0));
237};
238
239// Open search results in a viewer with zoom In/Out and Close buttons.
240const openJsonDataDialog = () => {
241 let fontSize = 14;
242
243 // Create overlay.
244 const overlay = document.createElement("div");
245 overlay.className = "modal-overlay";
246 overlay.onclick = (e) => {
247 if (e.target === overlay) {
248 document.body.removeChild(overlay);
249 }
250 };
251
252 // Modal box
253 const modal = document.createElement("div");
254 modal.className = "modal-box";
255
256 // Controls
257 const controls = document.createElement("div");
258 controls.className = "modal-controls";
259
260 const zoomInBtn = document.createElement("button");
261 zoomInBtn.textContent = "+";
262 zoomInBtn.onclick = () => {
263 fontSize += 2;
264 content.style.fontSize = fontSize + "px";
265 };
266
267 const zoomOutBtn = document.createElement("button");
268 zoomOutBtn.textContent = "-";
269 zoomOutBtn.onclick = () => {
270 fontSize = Math.max(10, fontSize - 2);
271 content.style.fontSize = fontSize + "px";
272 };
273
274 const closeBtn = document.createElement("button");
275 closeBtn.textContent = "Close";
276 closeBtn.className = "modal-close";
277 closeBtn.onclick = () => {
278 document.body.removeChild(overlay);
279 };
280
281 controls.appendChild(zoomInBtn);
282 controls.appendChild(zoomOutBtn);
283 controls.appendChild(closeBtn);
284
285 // Content
286 const content = document.createElement("pre");
287 content.className = "modal-content";
288 content.style.fontSize = fontSize + "px";
289 content.innerHTML = JSON.stringify(searchResults, null, 1);
290
291 modal.appendChild(controls);
292 modal.appendChild(content);
293 overlay.appendChild(modal);
294 document.body.appendChild(overlay);
295}
296
297// Convert a label to a valid ID by removing spaces.
298const labelToId = (label) => label.replace(/\s+/g, '');
299
300// Get the checkbox control based on the label.
301const checkBoxControl = (item) => {
302 const checkbox = leftSearchPanel.render.querySelector(`#${labelToId(item.label)}`);
303 return checkbox;
304};
305
306// Search the PDF document based on the search pattern and options.
307const searchPDF = (instance) => {
308
309 instance.Core.documentViewer.clearSearchResults();
310
311 // Get the search pattern from the search input field value in the search panel.
312 // SearchPattern can be something like "search*m" with "wildcard" option set to true.
313 // SearchPattern can be something like "search1|search2" with "regex" option set to true.
314 const searchPattern = leftSearchPanel.render.querySelector('#inputSearch').value.trim();
315
316 // Set search options values based on the checkbox states.
317 const searchOptions = {
318 caseSensitive: checkBoxControl(checkBoxData[0]).checked, // Match case.
319 wholeWord: checkBoxControl(checkBoxData[1]).checked, // Match whole words only.
320 wildcard: checkBoxControl(checkBoxData[2]).checked, // Allow using '*' as a wildcard value.
321 regex: checkBoxControl(checkBoxData[3]).checked, // String is treated as a regular expression.
322 ambientString: true, // Return ambient string as part of the result.
323 };
324
325 instance.UI.searchTextFull(searchPattern, searchOptions);
326};
327
328// Enable or disable a button based on the state.
329const enableButton = (button, state) => {
330 button.disabled = !state;
331 button.style.backgroundColor = (state) ? 'blue' : 'gray';
332 button.style.color = (state) ? 'white' : 'darkgray';
333};

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales