Add Watermark Showcase Demo Code

Easily add and edit watermarks on PDF documents. Watermark text is displayed as an overlay text on top of the original document.

This demo allows you to:

  • Choose your own PDF file
  • Add and edit watermark text
  • Customize styles of the watermark text

Implementation steps
To add watermarking capability with WebViewer:

Step 1: Choose your preferred web stack
Step 2: Download any 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 Add Watermark demo

1// ES6 Compliant Syntax
2// GitHub Copilot v1.0, GPT-4.1, September 22, 2025
3// File: index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7// Add Watermark Demo
8//
9// This code demonstrates the document watermarking capability in WebViewer, a JavaScript-based PDF SDK for web applications.
10// It allows the user to update the text and easily place an overlay text on top of the original document.
11//
12
13const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/arrest-warrant-signed.pdf';
14
15// Default colors for color picker
16const DEFAULT_COLORS = [
17 '#EB7532', // Orange
18 '#F7D038', // Yellow
19 '#A3E048', // Green
20 '#519A64', // Dark Green
21 '#33BBE6', // Blue
22 '#4355DB', // Dark Blue
23 '#D23BE7', // Purple
24 '#E6261F', // Red
25];
26
27// Default watermark properties
28let text = 'Hello World!';
29let size = 60;
30let opacity = 50;
31let color = DEFAULT_COLORS[0];
32let showTimestamp = true;
33
34// Customize UI section
35const customizeUI = async (instance) => {
36 // Load default document
37 await instance.Core.documentViewer.loadDocument(defaultDoc, {
38 extension: 'pdf'
39 });
40
41 drawWatermark(instance);
42};
43
44const drawWatermark = async (instance) => {
45 let watermarks = {
46 diagonal: {
47 text: text,
48 fontSize: size,
49 color: color,
50 opacity: opacity,
51 },
52 header: {
53 center: '',
54 fontSize: 10,
55 color: color,
56 opacity: 100,
57 }
58 };
59
60 if (showTimestamp) {
61 watermarks.header.center = `${new Date(Date.UTC(2000,0,0,0,0,0,0)).toTimeString()}`;
62 }
63
64 instance.Core.documentViewer.setWatermark({
65 ...watermarks,
66 });
67
68 instance.Core.documentViewer.refreshAll();
69 instance.Core.documentViewer.updateView();
70};
71
72
73// WebViewer section
74//
75// This code initializes the WebViewer with the basic settings
76// that are found in the default showcase WebViewer
77//
78
79const searchParams = new URLSearchParams(window.location.search);
80const history = window.history || window.parent.history || window.top.history;
81const licenseKey = 'YOUR_LICENSE_KEY';
82const element = document.getElementById('viewer');
83
84WebViewer({
85 path: '/lib',
86 licenseKey: licenseKey,
87}, element).then((instance) => {
88 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
89 const cloudyTools = [
90 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
91 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
92 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
93 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
94 ];
95 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
96 instance.UI.disableTools(cloudyTools);
97
98 // Set default toolbar group to Annotate
99 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
100
101 // Set default tool on mobile devices to Pan.
102 // https://apryse.atlassian.net/browse/WVR-3134
103 if (isMobileDevice()) {
104 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
105 }
106
107 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
108 if (searchParams.has('file')) {
109 searchParams.delete('file');
110 history.replaceState(null, '', '?' + searchParams.toString());
111 }
112 });
113
114 instance.Core.annotationManager.enableAnnotationNumbering();
115
116 instance.UI.NotesPanel.enableAttachmentPreview();
117
118 // Add the demo-specific functionality
119 customizeUI(instance).then(() => {
120 // Create UI controls after demo is initialized
121 createUIControls(instance);
122 });
123});
124
125// Function to check if the user is on a mobile device
126const isMobileDevice = () => {
127 return (
128 /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
129 window.navigator.userAgent
130 ) ||
131 /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
132 window.navigator.userAgent.substring(0, 4)
133 )
134 );
135}
136
137// Cleanup function for when the demo is closed or page is unloaded
138const cleanup = (instance) => {
139 if (typeof instance !== 'undefined' && instance.UI) {
140 // Clean up any resources if needed
141 console.log('Cleaning up complex-generation demo');
142 }
143};
144
145// Register cleanup for page unload
146window.addEventListener('beforeunload', () => cleanup(instance));
147window.addEventListener('unload', () => cleanup(instance));
148
149// UI section
150//
151// Helper code to add controls to the viewer holding the buttons
152// This code creates a container for the buttons, styles them, and adds them to the viewer
153//
154
155// Watermark text input
156const watermarkTextSection = (instance) => {
157 const section = document.createElement('div');
158 section.className = 'watermark-Text-Section';
159
160 const label = document.createElement('label');
161 label.textContent = 'Text';
162
163 section.appendChild(label);
164
165 const input = document.createElement('input');
166 input.className = 'watermark-text-input';
167 input.type = 'text';
168 input.value = text;
169
170 input.addEventListener('input', (e) => {
171 text = e.target.value;
172 drawWatermark(instance);
173 });
174
175 section.appendChild(input);
176
177 return section;
178};
179
180// Font Size slider
181const fontSizeSlider = (instance) => {
182 const section = document.createElement('div');
183 section.className = 'font-size-slider-section';
184
185 const label = document.createElement('label');
186 label.textContent = 'Font Size';
187
188 section.appendChild(label);
189
190 const slider = document.createElement('input');
191 slider.className = 'font-size-slider';
192 slider.type = 'range';
193 slider.defaultValue = '48';
194 slider.value = '48';
195 slider.min = '14';
196 slider.max = '200';
197 slider.step = '1';
198
199 slider.addEventListener('input', (e) => {
200 size = parseInt(e.target.value, 10);
201 drawWatermark(instance);
202 });
203
204 section.appendChild(slider);
205
206
207 return section;
208};
209
210// Opacity slider
211const opacitySlider = (instance) => {
212 const section = document.createElement('div');
213 section.className = 'opacity-slider-section';
214
215 const label = document.createElement('label');
216 label.textContent = 'Opacity';
217
218 section.appendChild(label);
219
220 const slider = document.createElement('input');
221 slider.className = 'font-size-slider';
222 slider.type = 'range';
223 slider.defaultValue = '48';
224 slider.value = '48';
225 slider.min = '0';
226 slider.max = '100';
227 slider.step = '1';
228
229 slider.addEventListener('input', (e) => {
230 opacity = parseInt(e.target.value, 10);
231 drawWatermark(instance);
232 });
233
234
235 section.appendChild(slider);
236
237 return section;
238};
239
240// Color picker
241const colorPicker = (instance) => {
242 const wrapper = document.createElement('div');
243
244 const colorDisplay = document.createElement('div');
245 colorDisplay.className = 'color-display';
246
247 const colorDisplayLabel = document.createElement('p');
248 colorDisplayLabel.className = 'color-display-label';
249 colorDisplayLabel.textContent = 'Color:';
250
251 const colorDisplayInput = document.createElement('input');
252 colorDisplayInput.className = 'color-display-input';
253 colorDisplayInput.type = 'text';
254 colorDisplayInput.readOnly = true;
255
256 colorDisplayInput.addEventListener('input', (e) => {
257 color = e.target.value;
258 drawWatermark(instance);
259 });
260
261 colorDisplay.appendChild(colorDisplayLabel);
262 colorDisplay.appendChild(colorDisplayInput);
263
264 wrapper.appendChild(colorDisplay);
265
266 const checkboxContainer = document.createElement('div');
267 checkboxContainer.className = 'checkbox-container';
268
269 DEFAULT_COLORS.forEach((default_color) => {
270 // Actual radio/checkbox input
271 const checkbox = document.createElement('input');
272 checkbox.className = 'checkbox-input';
273 checkbox.name = 'watermark-color';
274 checkbox.id = `checkbox-watermark-color-${default_color.replace('#', '')}`;
275 checkbox.type = 'radio';
276 checkbox.value = default_color;
277
278 if (default_color === color) {
279 checkbox.checked = true;
280 colorDisplayInput.value = `${default_color}`;
281 }
282
283 checkbox.onchange = (e) => {
284 if (e.target.checked) {
285 colorDisplayInput.value = `${e.target.value}`;
286 colorDisplayInput.dispatchEvent(new Event('input'));
287 }
288 };
289
290 // Hide to use custom checkbox instead
291 checkbox.style.display = 'none';
292
293 checkboxContainer.appendChild(checkbox);
294
295 // Custom checkbox
296 const span = document.createElement('span');
297 span.className = 'checkbox-checkmark';
298 span.id = `checkmark-watermark-color-${default_color.replace('#', '')}`;
299
300 if (checkbox.checked) {
301 span.textContent = '✓'; // U+2713
302 }
303
304 span.onclick = () => {
305
306 // Update all checkmarks of the same group
307 document.querySelectorAll(`input[name=watermark-color]`).forEach((cb) => {
308 const checkmark = document.getElementById(`checkmark-watermark-color-${cb.value.replace('#', '')}`);
309 if (checkmark) checkmark.textContent = '';
310 });
311 document.getElementById(`checkmark-watermark-color-${default_color.replace('#', '')}`).textContent = '✓'; // U+2713
312
313 checkbox.checked = true;
314 checkbox.onchange({ target: checkbox });
315 };
316
317 // Set each checkbox's color to a color in the default colors array
318 span.style.backgroundColor = default_color;
319 span.style.border = `3px solid ${default_color}`;
320
321 checkboxContainer.appendChild(span);
322 });
323
324 wrapper.appendChild(checkboxContainer);
325
326 return wrapper;
327};
328
329
330// Timestamp checkbox
331const timestampCheckbox = (instance) => {
332 const section = document.createElement('div');
333 section.className = 'timestamp-checkbox-section';
334
335 const checkbox = document.createElement('input');
336 checkbox.className = 'timestamp-checkbox';
337 checkbox.type = 'checkbox';
338 checkbox.checked = showTimestamp;
339
340 checkbox.onchange = (e) => {
341 showTimestamp = e.target.checked;
342 drawWatermark(instance);
343 };
344
345 checkbox.onchange = (e) => {
346 showTimestamp = e.target.checked;
347 drawWatermark(instance);
348 };
349
350 section.appendChild(checkbox);
351
352 const label = document.createElement('label');
353 label.className = 'timestamp-label';
354 label.textContent = 'Show timestamp';
355
356 section.appendChild(label);
357
358 return section;
359};
360
361const createUIControls = (instance) => {
362 // Create a container for all controls (label, dropdown, and buttons)
363 const controlsContainer = document.createElement('div');
364 controlsContainer.className = 'controls-container';
365
366 controlsContainer.appendChild(watermarkTextSection(instance));
367 controlsContainer.appendChild(fontSizeSlider(instance));
368 controlsContainer.appendChild(opacitySlider(instance));
369 controlsContainer.appendChild(colorPicker(instance));
370 controlsContainer.appendChild(timestampCheckbox(instance));
371
372 // Add the controls container to the viewer
373 element.insertBefore(controlsContainer, element.firstChild);
374};

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales