Redaction Showcase Demo Code Sample

Requirements
View Demo

Quickly search and redact sensitive text within documents, all handled securely on the client side. Redaction is performed entirely within your private network, ensuring sensitive data never leaves your environment.

This demo allows you to:

  • Support 30+ document types including PDF, MS Office (doc, docx, xlsx, pptx) and Images (jpg, png), all converted to PDF for processing
  • Load form local source or URL
  • Search using free-text or predefined patterns:
    • Phone Numbers
    • Credit Card Numbers
    • Emails
  • Customize search types:
    • Match Case
    • Match Whole Words
    • Wildcard '*'
    • Regular Expressions
    • Search Direction Up
    • Ambient String (returns surrounding strings to matches)
  • Download document as PDF with saved redactions

Implementation steps
To add Redaction capability with WebViewer:

Step 1: Choose 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.

License Key

1// ES6 Compliant Syntax
2// GitHub Copilot v1, Claude Sonnet 4 (Preview), October 5, 2025
3// File: showcase-demos/redaction/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
8
9// Global variables to track state
10let redactionDemoFile = "https://apryse.s3.amazonaws.com/public/files/samples/sales-invoice-with-credit-cards.pdf";
11const searchResults = []; // Store search results globally for access in other functions
12
13// Function to initialize and load the Redaction Tool
14function initializeWebViewer() {
15
16 const element = document.getElementById('viewer');
17 if (!element) {
18 console.error('Viewer div not found.');
19 return;
20 }
21
22 WebViewer({
23 path: '/lib',
24 initialDoc: redactionDemoFile,
25 licenseKey: licenseKey,
26 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
27 enableRedaction: true, // Enable redaction feature
28 backendType: WebViewer.BackendTypes.WASM, //required for redaction https://community.apryse.com/t/pdfworkererror-related-to-exclusive-lock-in-recursivesharedmutex-cpp-on-emscripten-platform/10059
29 fullAPI: true, // Required to use the PDFNet API
30 loadAsPDF: true,
31 disableElements: ['searchPanel', 'searchButton'], // Disable built-in search to prevent focus errors
32 }, element).then(instance => {
33
34 const { documentViewer } = instance.Core;
35
36 documentViewer.addEventListener('documentLoaded', () => {
37 instance.UI.openElements(['redactionPanel']);
38 instance.UI.disableElements(disabledElements);
39 instance.UI.addSearchListener(searchListener); //Handle search events to capture results for redaction
40 });
41
42 // UI Section
43 createUIElements();
44 });
45}
46
47// Function to apply redactions based on search results
48async function applyRedactions() {
49 const { documentViewer } = window.WebViewer.getInstance().Core;
50 const annotationManager = documentViewer.getAnnotationManager();
51 const annotations = await formatAnnotations(searchResults);
52 console.log('Global results', searchResults);
53
54 //Accessing the annotation manager to add and draw annotations
55 annotationManager.addAnnotations(annotations);
56 annotationManager.drawAnnotationsFromList(annotations);
57
58 // Apply redactions
59 annotationManager.applyRedactions();
60
61 // Clear search results and the searchResults array after applying redactions
62 documentViewer.clearSearchResults();
63 searchResults.length = 0;
64}
65
66// Search Listener, captures search results and adds redaction annotations
67// Only add it once to avoid multiple triggers
68const searchListener = (searchPattern, options, results) => {
69 const { UI } = window.WebViewer.getInstance();
70 addAnnotationsUsingSearchResult(results);
71 if (results.length > 0) {
72 UI.openElements(['redactionPanel']);
73 }
74 else
75 UI.closeElements(['redactionPanel']);
76
77 console.log('Search complete: ', searchPattern, options, results);
78};
79
80// Function to perform search and add redaction annotations
81function search(searchtext, searchOptions) {
82
83 const { documentViewer } = window.WebViewer.getInstance().Core;
84 const { UI } = window.WebViewer.getInstance();
85
86 const annotationManagerObj = documentViewer.getAnnotationManager();
87 const annotationList = annotationManagerObj.getAnnotationsList();
88 annotationManagerObj.deleteAnnotations(annotationList);
89 UI.searchTextFull(searchtext, searchOptions); // Perform the search with given options
90
91}
92
93// Function to format search results into redaction annotations
94async function formatAnnotations(results) {
95 const { documentViewer, Annotations } = window.WebViewer.getInstance().Core;
96 const annotationManager = documentViewer.getAnnotationManager();
97 const redactionList = annotationManager
98 .getAnnotationsList()
99 .filter((annot) => annot instanceof Annotations.RedactionAnnotation);
100
101 return await results.flatMap((r) => {
102 const annotation = new Annotations.RedactionAnnotation();
103 annotation.PageNumber = r.page_num;
104 annotation.Quads = r.quads.map((quad) => quad.getPoints());
105 annotation.StrokeColor = new Annotations.Color(0, 255, 0);
106 annotation.setContents(r.result_str);
107 annotation.Author = 'Guest';
108 annotation.setCustomData(
109 'trn-annot-preview',
110 documentViewer.getSelectedText(annotation.PageNumber)
111 );
112 if (redactionList.some((r) => r.getContents() === annotation.getContents())) {
113 return [];
114 }
115 return [annotation];
116 });
117}
118
119// Function to add annotations using search results
120// This function is called from the search listener
121async function addAnnotationsUsingSearchResult(results) {
122 const { documentViewer } = window.WebViewer.getInstance().Core;
123 const annotationManager = documentViewer.getAnnotationManager();
124
125 //Keep results in global variable to access later if needed
126 searchResults.push(...results);
127 console.log('results', results);
128 const annotations = await formatAnnotations(results);
129 annotationManager.addAnnotations(annotations);
130 annotationManager.drawAnnotationsFromList(annotations);
131};
132
133// Search options for redaction
134// You can modify these options or add more as needed
135const searchOptions = {
136 caseSensitive: true, // match case
137 wholeWord: true, // match whole words only
138 wildcard: false, // allow using '*' as a wildcard value
139 regex: false, // string is treated as a regular expression
140 searchUp: false, // search from the end of the document upwards
141 ambientString: true, // return ambient string as part of the result
142};
143
144// Sample redaction search patterns using regex
145// You can modify or add more patterns as needed
146// WebViewer implements its own pattern similar to these below, here we define our own for the redaction demo
147const redactionSearchSamples = [
148 {
149 label: 'Phone Numbers',
150 value: '\\b(?:\\+?1[-\\s]?)?(?:\\(?[0-9]{3}\\)?[-\\s]?)[0-9]{3}[-\\s]?[0-9]{4}\\b',
151 },
152 {
153 label: 'Emails',
154 value: '\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}\\b',
155 },
156 {
157 label: 'Credit Card Numbers',
158 value: '\\b(?:\\d[ -]*?){13,16}\\b',
159 },
160];
161
162//UI Elements to disable
163const disabledElements = [
164 'toolbarGroup-Shapes',
165 'toolbarGroup-View',
166 'toolbarGroup-Insert',
167 'toolbarGroup-Annotate',
168 'toolbarGroup-FillAndSign',
169 'toolbarGroup-Forms',
170 'toolbarGroup-Edit',
171 'toolbarGroup-Measure',
172];
173
174// UI Elements
175// ui-elements.js
176// Function to create and initialize UI elements
177function createUIElements() {
178 // Create a container for all controls (label, dropdown, and buttons)
179 // Dynamically load ui-elements.js if not already loaded
180 if (!window.SidePanel) {
181 const script = document.createElement('script');
182 script.src = '/showcase-demos/redaction/ui-elements.js';
183 script.onload = () => {
184 UIElements.init('viewer', searchResults);
185 UIElements.handleException(); //Add handling of Reacts focus error on this JavaScript sample.
186 };
187 document.head.appendChild(script);
188 }
189}
190
191//Make functions accessible globally
192window.redactionSearchSamples = redactionSearchSamples;
193window.searchOptions = searchOptions;
194window.applyRedactions = applyRedactions;
195window.search = search;
196
197// Initialize the WebViewer
198initializeWebViewer();
199

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales