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

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales