Annotations Permissions Showcase Demo Code Sample

Requirements
View Demo

Enable customization of user permissions to interact with annotations in the PDF file, completely client-side with three different levels of permissions:

  • Administrator
  • User
  • Read-Only

This demo lets you:

  • Upload a PDF file and add user permissions
  • Add permissions to users as administrator, user, or read-only
  • Interact with annotations in the PDF file according to the set permission

Implementation steps
To add annotations permissions capability to a PDF 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
2/* ES6 Compliant Syntax */
3/* GitHub Copilot v1.0, Claude 3.5 Sonnet, September 1, 2025 */
4/* File: index.js */
5
6import WebViewer from '@pdftron/webviewer';
7
8// Annotation Permission section
9//
10// Code to customize user permissions, completely client-side with 3
11// different levels of permissions: administrator, user, and read-only
12//
13
14// Default Document
15const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf';
16
17// Set default user
18let currentUser = 'Justin';
19
20// List of users with permissions
21let userList = {
22 Justin: { permissions: 'administrator', canView: true, hidden: [] },
23 Sally: { permissions: 'user', canView: true, hidden: [] },
24 Brian: { permissions: 'read-only', canView: true, hidden: [] }
25};
26
27// Annotation types that can be toggled for visibility
28let toggleableTypes = [];
29
30// Customize UI
31const customizeUI = async (instance) => {
32 const { Annotations } = instance.Core;
33
34 // Load default document
35 await instance.Core.documentViewer.loadDocument(defaultDoc, {
36 extension: 'pdf',
37 });
38 instance.UI.setToolbarGroup('toolbarGroup-Annotate', true);
39
40 // Add sticky note, free hand, and highlight to toggleable types
41 toggleableTypes = [
42 {
43 displayName: 'Sticky notes',
44 annotationType: Annotations.StickyAnnotation,
45 },
46 {
47 displayName: 'Free hand',
48 annotationType: Annotations.FreeHandAnnotation,
49 },
50 {
51 displayName: 'Highlight',
52 annotationType: Annotations.TextHighlightAnnotation,
53 },
54 ];
55
56 // Set user data for mentions in notes tool
57 const userData = Object.keys(userList).map((user) => ({
58 value: user,
59 email: `${user.toLowerCase()}@pdftron.com`,
60 }));
61 instance.UI.mentions.setUserData(userData);
62
63 // Set default user in the WebViewer
64 setUser(instance, currentUser);
65};
66
67// Set selected user in the WebViewer
68const setUser = (instance, username) => {
69 const { annotationManager }= instance.Core;
70 annotationManager.setCurrentUser(username);
71 const permissions = userList[username].permissions;
72
73 if (permissions === 'administrator') {
74 annotationManager.promoteUserToAdmin();
75 annotationManager.disableReadOnlyMode();
76 } else if (permissions === 'read-only') {
77 annotationManager.enableReadOnlyMode();
78 annotationManager.demoteUserFromAdmin();
79 } else {
80 annotationManager.disableReadOnlyMode();
81 annotationManager.demoteUserFromAdmin();
82 }
83
84 currentUser = username;
85 setAnnotationsForUser(instance);
86 updateUIControls();
87};
88
89// Set annotations for current user
90const setAnnotationsForUser = (instance) => {
91 const { annotationManager } = instance.Core;
92
93 const { hidden } = userList[currentUser];
94
95 // First get a list of all the types that should be hidden
96 const hiddenTypeMap = toggleableTypes.reduce((acc, type) => {
97 if (hidden.indexOf(type.displayName) > -1) {
98 acc.push(type.annotationType);
99 }
100 return acc;
101 }, []);
102
103 const allAnnots = annotationManager.getAnnotationsList();
104 const toShow = [];
105 const toHide = [];
106
107 // Generate lists of annotations to show and hide
108 allAnnots.forEach((annot) => {
109 const isType = hiddenTypeMap.some((type) => annot instanceof type);
110 if (isType) {
111 toHide.push(annot);
112 } else {
113 toShow.push(annot);
114 }
115 });
116
117 // Show and hide annotations
118 annotationManager.showAnnotations(toShow);
119 annotationManager.hideAnnotations(toHide);
120};
121
122// Add user to user list
123const addUser = (instance, name, p) => {
124 userList = {
125 ...userList,
126 [name]: { permissions: p, canView: true, hidden: [] },
127 };
128};
129
130// Toggle annotation type visibility for current user
131const toggleAnnotations = (instance, type) => {
132 const { displayName } = type;
133 const { hidden } = userList[currentUser];
134 const idx = hidden.indexOf(displayName);
135 const newArray = hidden.slice(0);
136
137 if (idx !== -1) {
138 newArray.splice(idx, 1);
139 } else {
140 newArray.push(displayName);
141 }
142
143 userList = {
144 ...userList,
145 [currentUser]: {
146 ...userList[currentUser],
147 hidden: newArray
148 }
149 };
150
151 setAnnotationsForUser(instance);
152 updateUIControls();
153};
154
155// Check if annotation type is visible for current user
156const isChecked = (type) => {
157 if (!currentUser) return;
158 const { displayName } = type;
159 const user = currentUser;
160 return userList[user].hidden.indexOf(displayName) === -1;
161};
162
163
164// Helper functions for configuration snippet modal
165const perm = () => {
166 return currentUser ? userList[currentUser].permissions : null;
167}
168const hiddenList = () => {
169 return currentUser ? userList[currentUser].hidden : [];
170};
171
172let text = '';
173let annotText = '';
174
175const setText = () => {
176 const permission = perm();
177 if (permission === 'administrator') {
178 text = 'annotationManager.promoteUserToAdmin()';
179 } else if (permission === 'read-only') {
180 text = 'annotationManager.enableReadOnlyMode()';
181 } else {
182 text = 'annotationManager.demoteUserFromAdmin();\n annotationManager.disableReadOnlyMode();';
183 }
184
185 return text;
186};
187
188const setAnnotText = () => {
189 const list = hiddenList();
190 if (list.length === 0) {
191 annotText = `
192 annotationManager.showAnnotations(allAnnots);
193 `;
194 } else {
195 let ifStatement = list.reduce((acc, hidden) => {
196 if (hidden === 'Sticky notes') {
197 acc += ' annot instanceof Annotations.StickyAnnotation || \n';
198 }
199 if (hidden === 'Free hand') {
200 acc += ' annot instanceof Annotations.FreeHandAnnotation || \n';
201 }
202 if (hidden === 'Highlight') {
203 acc += ' annot instanceof Annotations.TextHighlightAnnotation || \n';
204 }
205
206 return acc;
207 }, '');
208
209 ifStatement = ifStatement.substring(8, ifStatement.length - 5);
210
211 annotText = `
212 const hideList = allAnnots.filter(annot => {
213 return ${ifStatement};
214 });
215 annotationManager.hideAnnotations(hideList);
216 `;
217 }
218
219 return annotText;
220};
221
222// WebViewer section
223//
224// This code initializes the WebViewer with the basic settings
225// that are found in the default showcase WebViewer
226//
227
228const searchParams = new URLSearchParams(window.location.search);
229const history = window.history || window.parent.history || window.top.history;
230const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
231const element = document.getElementById('viewer');
232
233// Initialize WebViewer with the specified settings
234WebViewer({
235 path: '/lib',
236 licenseKey: licenseKey,
237 enableFilePicker: true,
238}, element).then((instance) => {
239 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
240 const cloudyTools = [
241 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
242 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
243 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
244 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
245 ];
246 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
247 instance.UI.disableTools(cloudyTools);
248
249 // Set default toolbar group to Annotate
250 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
251
252 // Set default tool on mobile devices to Pan.
253 // https://apryse.atlassian.net/browse/WVR-3134
254 if (isMobileDevice()) {
255 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
256 }
257
258 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
259 if (searchParams.has('file')) {
260 searchParams.delete('file');
261 history.replaceState(null, '', '?' + searchParams.toString());
262 }
263 });
264
265 instance.Core.annotationManager.enableAnnotationNumbering();
266
267 instance.UI.NotesPanel.enableAttachmentPreview();
268
269 // Add the demo-specific functionality
270 customizeUI(instance).then(() => {
271 // Create UI controls after demo is initialized
272 createUIControls(instance);
273 });
274});
275
276// Function to check if the user is on a mobile device
277const isMobileDevice = () => {
278 return (
279 navigator.userAgentData?.mobile ??
280 /android|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile/i.test(
281 window.navigator.userAgent
282 )
283 );
284}
285
286// Cleanup function for when the demo is closed or page is unloaded
287const cleanup = (instance) => {
288 const { documentViewer } = instance.Core;
289
290 if (typeof instance !== 'undefined' && instance.UI) {
291
292 // Reset annotation user to default 'Guest' and promote to admin
293 const annotationManager = documentViewer.getAnnotationManager();
294 annotationManager.setCurrentUser('Guest');
295 annotationManager.promoteUserToAdmin();
296 annotationManager.disableReadOnlyMode();
297
298 console.log('Cleaning up compare-files demo');
299 }
300};
301
302// Register cleanup for page unload
303window.addEventListener('beforeunload', () => cleanup());
304window.addEventListener('unload', () => cleanup());
305
306
307// UI section
308//
309// Helper code to add controls to the viewer holding the buttons
310// This code creates a container for the buttons, styles them, and adds them to the viewer
311//
312
313// User selection
314const userPageSection = (instance) => {
315 // Create a wrapper div for the user section
316 const wrapper = document.createElement('div');
317 wrapper.className = 'user-page-section';
318
319 // Select User label
320 const selectUserLabel = document.createElement('h2');
321 selectUserLabel.className = 'header select-user-header';
322 selectUserLabel.textContent = 'Select User';
323
324 wrapper.appendChild(selectUserLabel);
325
326 // Users buttons container
327 const buttonsContainer = document.createElement('div');
328 buttonsContainer.className = 'buttons-container';
329
330 Object.keys(userList).forEach((username) => {
331 const button = document.createElement('button');
332 button.className = 'btn btn-user';
333 button.textContent = `${username} (${userList[username].permissions})`;
334 button.onclick = () => {
335 setUser(instance, username);
336 };
337
338 buttonsContainer.appendChild(button);
339 });
340 wrapper.appendChild(buttonsContainer);
341
342 // Add User clickable label
343 const addUserLabel = document.createElement('label');
344 addUserLabel.className = 'add-user-label';
345 addUserLabel.textContent = 'Add user';
346 addUserLabel.onclick = () => {
347 const addUserContainer = document.querySelector('.add-user-container');
348 if (addUserContainer.classList.contains('visible')) {
349 addUserContainer.classList.remove('visible');
350 addUserLabel.textContent = 'Add user';
351 } else {
352 addUserContainer.classList.add('visible');
353 addUserLabel.textContent = 'Close';
354 }
355 };
356
357 wrapper.appendChild(addUserLabel);
358
359 // Add User container
360 const addUserContainer = document.createElement('div');
361 addUserContainer.className = 'add-user-container';
362
363 // Add User input field
364 const input = document.createElement('input');
365 input.type = 'text';
366 input.placeholder = 'Username';
367 input.className = 'input add-user-input';
368
369 addUserContainer.appendChild(input);
370
371 // Add User permission dropdown
372 const permission = document.createElement('select');
373 permission.className = 'input add-user-permission';
374 permission.options.add(new Option('Administrator', 'administrator'));
375 permission.options.add(new Option('User', 'user'));
376 permission.options.add(new Option('Read-Only', 'read-only'));
377
378 addUserContainer.appendChild(permission);
379
380 // Add User add button
381 const addButton = document.createElement('button');
382 addButton.className = 'btn btn-submit-user';
383 addButton.textContent = 'Add';
384 addButton.onclick = () => {
385 // Validate input
386 const name = input.value;
387 if (name === '') return;
388 const p = permission.value;
389
390 // Add user to user list
391 addUser(instance, name, p);
392
393 // Add button for new user
394 const buttonsContainer = document.querySelector('.buttons-container');
395 const button = document.createElement('button');
396 button.className = 'btn btn-user';
397 button.textContent = `${name} (${p})`;
398 button.onclick = () => {
399 setUser(instance, name);
400 };
401 buttonsContainer.appendChild(button);
402
403 // Reset input fields
404 input.value = '';
405 permission.options.selectedIndex = 0;
406
407 // Close add user section
408 addUserLabel.click();
409 };
410
411 addUserContainer.appendChild(addButton);
412
413 wrapper.appendChild(addUserContainer);
414
415 return wrapper;
416};
417
418// Role permissions description
419const rolePermissionsPageSection = () => {
420 const wrapper = document.createElement('div');
421 wrapper.className = 'role-permissions-section';
422
423 const rolePermissionsLabel = document.createElement('h2');
424 rolePermissionsLabel.className = 'header role-permissions-header';
425 rolePermissionsLabel.textContent = 'Role Permissions';
426
427 wrapper.appendChild(rolePermissionsLabel);
428
429 const rolePermissionDescription = document.createElement('p');
430 rolePermissionDescription.className = 'text role-permission-paragraph';
431 const permission = perm();
432 if (permission === 'administrator') {
433 rolePermissionDescription.innerHTML = '<b> Admin: </b> Can add, edit, or remove any annotations created by anyone';
434 } else if (permission === 'read-only') {
435 rolePermissionDescription.innerHTML = '<b> Read-Only: </b> Can only view annotations';
436 } else { // user
437 rolePermissionDescription.innerHTML = '<b> User: </b> Can create, and edit or remove annotations created by themself';
438 }
439
440 wrapper.appendChild(rolePermissionDescription);
441
442 return wrapper;
443};
444
445// Viewing permissions checkboxes for selected user
446const viewingPermissionsPageSection = (instance) => {
447 const wrapper = document.createElement('div');
448 wrapper.className = 'viewing-permissions-section';
449
450 // Viewing Permissions label
451 const viewingPermissionsLabel = document.createElement('label');
452 viewingPermissionsLabel.className = 'header viewing-permissions-label';
453 viewingPermissionsLabel.textContent = `Set viewing permissions for ` + (currentUser ? currentUser : '...');
454
455 wrapper.appendChild(viewingPermissionsLabel);
456
457 // Viewing Permissions for each annotation type
458 const checkboxContainer = document.createElement('div');
459 checkboxContainer.className = 'checkbox-container';
460
461 toggleableTypes.forEach((type) => {
462 const checkboxRow = document.createElement('div');
463
464 const checkbox = document.createElement('input');
465 checkbox.type = 'checkbox';
466 checkbox.id = `view-${type.displayName}-checkbox`;
467 checkbox.checked = isChecked(type);
468 checkbox.onchange = () => {
469 toggleAnnotations(instance, type);
470 };
471
472 checkboxRow.appendChild(checkbox);
473
474 const label = document.createElement('label');
475 label.className = 'text checkbox-label';
476 label.ariaLabel = `Toggle ${type.displayName} annotations`;
477 label.textContent = `${type.displayName}`;
478
479 checkboxRow.appendChild(label);
480 checkboxContainer.appendChild(checkboxRow);
481 });
482 wrapper.appendChild(checkboxContainer);
483
484 return wrapper;
485};
486
487
488// Helper function to create UI controls
489const createUIControls = (instance) => {
490 // Create a container for all controls (label, dropdown, and buttons)
491 const controlsContainer = document.createElement('div');
492 controlsContainer.className = 'controls-container';
493
494 // Add user section
495 controlsContainer.appendChild(userPageSection(instance));
496
497 // Add role permissions and viewing permissions sections side by side
498 const roleViewingPermissionsContainer = document.createElement('div');
499 roleViewingPermissionsContainer.className = 'role-viewing-permissions-container';
500 roleViewingPermissionsContainer.appendChild(rolePermissionsPageSection());
501 roleViewingPermissionsContainer.appendChild(viewingPermissionsPageSection(instance));
502 controlsContainer.appendChild(roleViewingPermissionsContainer);
503
504
505
506 element.insertBefore(controlsContainer, element.firstChild);
507};
508
509// Helper function to update UI controls
510const updateUIControls = () => {
511 // Update role permission description
512 const rolePermissionDescription = document.querySelector('.role-permission-paragraph');
513 if (rolePermissionDescription) {
514 const permission = perm();
515 if (permission === 'administrator') {
516 rolePermissionDescription.innerHTML = '<b> Admin: </b> Can add, edit, or remove any annotations created by anyone';
517 } else if (permission === 'read-only') {
518 rolePermissionDescription.innerHTML = '<b> Read-Only: </b> Can only view annotations';
519 } else { // user
520 rolePermissionDescription.innerHTML = '<b> User: </b> Can create, and edit or remove annotations created by themself';
521 }
522 }
523
524 // Update viewing permissions label
525 const viewingPermissionsLabel = document.querySelector('.viewing-permissions-label');
526 if (viewingPermissionsLabel) {
527 viewingPermissionsLabel.textContent = `Set viewing permissions for ` + (currentUser ? currentUser : '...');
528 }
529
530 // Update checkboxes
531 toggleableTypes.forEach((type) => {
532 const checkbox = document.getElementById(`view-${type.displayName}-checkbox`);
533 if (checkbox) {
534 checkbox.checked = isChecked(type);
535 }
536 });
537
538 // Update configuration snippet text
539 const codeBlock = document.getElementById('config-snippet-code-block');
540 if (codeBlock) {
541 codeBlock.textContent = `const wvElement = document.getElementById('viewer');
542WebViewer({ ...options }, wvElement)
543.then(instance => {
544 const { annotationManager } = instance.Core;
545 annotationManager.setCurrentUser('${currentUser}');
546 ${setText()}
547 const allAnnots = annotationManager.getAnnotationsList();
548 ${setAnnotText()}
549})`;
550 }
551};

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales