Easily perform changes to PDF pages, including split, merge, append, replicate, reorder, and more.
This demo allows you to:
Implementation steps
To add PDF Page Manipulation capability with WebViewer:
Step 1: Get started with WebViewer in 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.
Apryse collects some data regarding your usage of the SDK for product improvement.
The data that Apryse collects include:
For clarity, no other data is collected by the SDK and Apryse has no access to the contents of your documents.
If you wish to continue without data collection, contact us and we will email you a no-tracking trial key for you to get started.
1
2// ES6 Compliant Syntax
3// GitHub Copilot - GPT-4 Model - October 16, 2025
4// File: pdf-page-manipulation-api/index.js
5
6import WebViewer from '@pdftron/webviewer';
7import { saveAs } from 'file-saver';
8// Global variables
9const element = document.getElementById('viewer');
10let instance = null;
11
12// Initialize WebViewer.
13function initializeWebViewer() {
14 if (!element) {
15 console.error('Viewer div not found.');
16 return;
17 }
18 // Initialize WebViewer.
19 WebViewer({
20 path: '/lib',
21 licenseKey: 'YOUR_LICENSE_KEY',
22 initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf',
23 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File.
24 }, element).then((inst) => {
25 instance = inst;
26 const { documentViewer} = instance.Core;
27 documentViewer.addEventListener('documentLoaded', () => {
28 const doc = documentViewer.getDocument();
29 console.log(`Document loaded with ${doc.getPageCount()} pages.`);
30 createUIElements().then(() => {
31 // Now initialize rotateAngle options values (after UI elements are created).
32 if (window.rotateAngle) {
33 window.rotateAngle.children[0].value = instance.Core.PageRotation.E_90;
34 window.rotateAngle.children[1].value = instance.Core.PageRotation.E_180;
35 window.rotateAngle.children[2].value = instance.Core.PageRotation.E_270;
36 } else {
37 console.error('rotateAngle element not found after UI creation');
38 }
39 // Activate thumbnails panel.
40 instance.UI.openElements(['tabPanel']);
41 instance.UI.setActiveTabInPanel({ tabPanel: 'tabPanel', tabName: 'thumbnailsPanel' });
42 });
43 });
44 });
45} // Close initializeWebViewer function.
46
47// Validate page number input.
48const validPageNumber = (pageNum, doc) => {
49 if (pageNum > doc.getPageCount() || pageNum < 1) {
50 alert('Please enter a valid page number.');
51 return false;
52 }
53 return true;
54}
55
56// Convert a string like "1,2,5-7" to an array of numbers [1,2,5,6,7].
57const parseStringToNumberArray = (val, maxVal) => {
58 // Check for valid number, comma, or dash pattern.
59 if (!/^[0-9]+([,-][0-9]+)*$/.test(val)) {
60 return [];
61 }
62 const numbers = [];
63 val.split(',').forEach((group) => {
64 const range = group.split('-');
65 const start = Number(range[0]);
66 let end = Number(range.length === 1 ? range[0] : range[1]);
67 if (end > maxVal) {
68 end = maxVal;
69 }
70 for (let i = start; i <= end; i++) {
71 numbers.push(i);
72 }
73 });
74 return numbers;
75}
76
77// Delete specified pages from document.
78const deletePages = () => {
79 const doc = instance.Core.documentViewer.getDocument();
80 const pageNumbers = pagesRange.value;
81 const result = parseStringToNumberArray(pageNumbers, doc.getPageCount());
82 if (!validPageNumber(pageNumbers, doc)) return;
83 if (result.length === doc.getPageCount()) {
84 alert('Cannot delete all pages in the document.');
85 return;
86 }
87 if (result.length > 0) {
88 doc.removePages(result);
89 } else {
90 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
91 }
92}
93
94// Rotate specified pages in document.
95const rotatePages = () => {
96 const pageNumbers = pagesRange.value;
97 const rotation = Number(rotateAngle.value);
98 const doc = instance.Core.documentViewer.getDocument();
99 if (!validPageNumber(pageNumbers, doc)) return;
100 let result = [];
101 if (pageNumbers) {
102 result = parseStringToNumberArray(pageNumbers, doc.getPageCount());
103 } else {
104 // No page numbers provided, rotating all pages.
105 for (let i = 1; i <= doc.getPageCount(); i++) {
106 result.push(i);
107 }
108 }
109 if (result.length > 0) {
110 doc.rotatePages(result, rotation);
111 } else {
112 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
113 }
114}
115
116// Move a page to a new location in the document.
117const movePage = () => {
118 const doc = instance.Core.documentViewer.getDocument();
119 const pageOne = Number(pageInput.value);
120 let pageTwo = Number(locationInput.value);
121 if (!validPageNumber(pageOne, doc) || !validPageNumber(pageTwo, doc)) return;
122 if (pageOne < pageTwo) pageTwo++; // adjust for removing the "from" page
123 doc.movePages([pageOne], pageTwo);
124}
125
126// Insert a blank page at the specified location.
127const insertBlankPage = () => {
128 let pageNumber = Number(pageInput.value);
129 const doc = instance.Core.documentViewer.getDocument();
130 if (pageNumber === -1) { // Append to end.
131 pageNumber = doc.getPageCount() + 1;
132 } else if (!validPageNumber(pageNumber, doc)) return;
133 const pageSize = doc.getPageInfo(1);
134 doc.insertBlankPages([pageNumber], pageSize.width, pageSize.height);
135}
136
137// Extract specified pages from document and save as a new file.
138const extractPages = async () => {
139 const pageNumbers = pagesRange.value;
140 const doc = instance.Core.documentViewer.getDocument();
141 const pageCount = doc.getPageCount();
142 if (!validPageNumber(pageNumbers, doc)) return;
143 let pagesToExtract = [];
144 if (pageNumbers) {
145 pagesToExtract = parseStringToNumberArray(pageNumbers, pageCount);
146 } else {
147 for (let i = 1; i <= pageCount; i++) {
148 pagesToExtract.push(i);
149 }
150 }
151 if (pagesToExtract.length > 0) {
152 const annotList = instance.Core.annotationManager.getAnnotationsList().filter((annot) => pagesToExtract.indexOf(annot.PageNumber) > -1);
153 const xfdfString = await instance.Core.annotationManager.exportAnnotations({ annotationList: annotList });
154 doc.extractPages(pagesToExtract, xfdfString).then((data) => {
155 const arr = new Uint8Array(data);
156 const blob = new Blob([arr], { type: 'application/pdf' });
157 saveAs(blob, 'extracted.pdf');
158 });
159 } else {
160 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
161 }
162}
163
164// Crop the specified page by the specified pixel amounts from 4 sides.
165const cropPage = () => {
166 const pageNumber = Number(pageInput.value);
167 const doc = instance.Core.documentViewer.getDocument();
168 if (!validPageNumber(pageNumber, doc)) return;
169 const top = Number(cropTop.value);
170 const bottom = Number(cropBottom.value);
171 const left = Number(cropLeft.value);
172 const right = Number(cropRight.value);
173 doc.cropPages([pageNumber], top, bottom, left, right);
174}
175
176// Merge an uploaded document into the current document at the specified location.
177const mergeDocuments = () => {
178 if (!window.newDoc) {
179 alert('Please upload a document to merge first');
180 return;
181 }
182 const pageNumbers = pagesRange.value;
183 let pagesToInsert = [];
184 if (pageNumbers) {
185 pagesToInsert = parseStringToNumberArray(pageNumbers, window.newDoc.getPageCount());
186 } else {
187 for (let i = 1; i <= window.newDoc.getPageCount(); i++) {
188 pagesToInsert.push(i);
189 }
190 }
191 const locationToInsert = Number(locationInput.value);
192 if (pagesToInsert.length > 0) {
193 instance.Core.documentViewer.getDocument().insertPages(window.newDoc, pagesToInsert, locationToInsert);
194 } else {
195 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
196 }
197}
198
199// Expose functions to global scope for UI elements.
200window.deletePages = deletePages;
201window.rotatePages = rotatePages;
202window.movePage = movePage;
203window.insertBlankPage = insertBlankPage;
204window.extractPages = extractPages;
205window.cropPage = cropPage;
206window.mergeDocuments = mergeDocuments;
207window.parseStringToNumberArray = parseStringToNumberArray;
208window.validPageNumber = validPageNumber;
209
210// UI Elements
211// Function to create and initialize UI elements.
212function createUIElements() {
213 return new Promise((resolve) => {
214 // Create a container for all controls (label, dropdown, and buttons).
215 // Dynamically load ui-elements.js, if not already loaded.
216 if (typeof UIElements === 'undefined') {
217 const script = document.createElement('script');
218 script.src = '/showcase-demos/pdf-page-manipulation-api/ui-elements.js';
219 script.onload = () => {
220 console.log('ui-elements.js loaded successfully');
221 UIElements.init(instance);
222 resolve(); // Resolve when UI elements are created.
223 };
224 script.onerror = () => {
225 console.error('Failed to load ui-elements.js');
226 resolve(); // Resolve even on error to prevent hanging.
227 };
228 document.head.appendChild(script);
229 } else {
230 console.log('UIElements already available, initializing...');
231 UIElements.init(instance);
232 resolve(); // Resolve immediately if UIElements is already loaded.
233 }
234 });
235}
236
237// Initialize WebViewer when the window loads.
238initializeWebViewer();
1// ES6 Compliant Syntax
2// GitHub Copilot, Claude Sonnet 4 (Preview), October 16, 2025
3// File: showcase-demos/pdf-page-manipulation-api/ui-elements.js
4
5class UIElements {
6
7 static init(instance) {
8 this.createUIElements(instance);
9 }
10
11 static createUIElements(instance) {
12 const element = document.getElementById('viewer');
13 let newDoc = null; // document to merge into the current document
14
15 // Check if controls container already exists.
16 let controlsContainer = document.getElementById('controls-container');
17 if (controlsContainer) {
18 console.log('Controls container already exists, skipping creation');
19 return; // Exit early if container already exists.
20 }
21
22 // UI section
23 // Create a container and insert the controls in it.
24 controlsContainer = document.createElement('div');
25 const commandsList = document.createElement('select');
26 controlsContainer.appendChild(commandsList);
27 const uploadButton = document.createElement('input');
28 controlsContainer.appendChild(uploadButton);
29 const pagesRange = document.createElement('input');
30 controlsContainer.appendChild(pagesRange);
31 const rotateAngle = document.createElement('select');
32 window.rotateAngle = rotateAngle; // Expose to global scope for function access.
33 controlsContainer.appendChild(rotateAngle);
34 const topLabel = document.createElement('label');
35 topLabel.textContent = 'Top';
36 controlsContainer.appendChild(topLabel);
37 const cropTop = document.createElement('input');
38 controlsContainer.appendChild(cropTop);
39 const bottomLabel = document.createElement('label');
40 bottomLabel.textContent = 'Bottom';
41 controlsContainer.appendChild(bottomLabel);
42 const cropBottom = document.createElement('input');
43 controlsContainer.appendChild(cropBottom);
44 const leftLabel = document.createElement('label');
45 leftLabel.textContent = 'Left';
46 controlsContainer.appendChild(leftLabel);
47 const cropLeft = document.createElement('input');
48 controlsContainer.appendChild(cropLeft);
49 const rightLabel = document.createElement('label');
50 rightLabel.textContent = 'Right';
51 controlsContainer.appendChild(rightLabel);
52 const cropRight = document.createElement('input');
53 controlsContainer.appendChild(cropRight);
54 cropTop.type = cropBottom.type = cropLeft.type = cropRight.type = 'number';
55 cropTop.className = cropBottom.className = cropLeft.className = cropRight.className = 'elements-style';
56 cropTop.value = cropBottom.value = cropLeft.value = cropRight.value = 50;
57
58 const pageInput = document.createElement('input');
59 controlsContainer.appendChild(pageInput);
60 const locationInput = document.createElement('input');
61 controlsContainer.appendChild(locationInput);
62 const runCommandButton = document.createElement('button');
63 controlsContainer.appendChild(runCommandButton);
64 const br = document.createElement('br');
65 controlsContainer.appendChild(br);
66 const promptLabel = document.createElement('label');
67 controlsContainer.appendChild(promptLabel);
68 controlsContainer.className = 'control-container';
69
70 uploadButton.type = 'file';
71 uploadButton.accept = '.pdf,.jpg,.jpeg,.png';
72 uploadButton.onchange = async (e) => {
73 const file = e.target.files[0];
74 const split = file.name.split('.');
75 const ext = split[split.length - 1];
76 newDoc = await instance.Core.createDocument(file, {
77 extension: ext,
78 filename: file.name,
79 });
80
81 // Update the global reference so it's available for merge operations.
82 window.newDoc = newDoc;
83 console.log('File uploaded and newDoc updated:', file.name);
84 }
85
86 rotateAngle.className = commandsList.className = 'list-style';
87 pagesRange.className = pageInput.className = locationInput.className = 'elements-style';
88 uploadButton.className = runCommandButton.className = 'btn';
89 pagesRange.type = 'text';
90 pagesRange.placeholder = '1,2,3-5';
91 [90, 180, 270].forEach(deg => { // Create rotate angle UI elements.
92 const option = document.createElement('option');
93 //option.value will be set after WebViewer is initialized because values are in instance.Core.PageRotation.
94 option.textContent = deg.toString()+'\u00b0'; // Degree symbol °
95 rotateAngle.appendChild(option);
96 });
97 pageInput.type = 'text';
98 pageInput.placeholder = 'Page #';
99 locationInput.type = 'text';
100 locationInput.placeholder = 'to page #';
101 runCommandButton.textContent = 'Run Command';
102
103
104
105 runCommandButton.onclick = () => commandsArray[commandsList.selectedIndex].executeCommand();
106 const commandsArray = [
107 { value: 'Select Command', executeCommand: () => alert('Select a command from the list'), visibleItems: [],
108 text: 'Select a command from the list' },
109 { value: 'Rotate pages', executeCommand: () => window.rotatePages(), visibleItems: [pagesRange, rotateAngle],
110 text: 'Enter a comma or dash separated list of page numbers to rotate (leave empty for all pages)' },
111 { value: 'Delete pages', executeCommand: () => window.deletePages(), visibleItems: [pagesRange],
112 text: 'Enter a comma or dash separated list of page numbers to delete' },
113 { value: 'Move page', executeCommand: () => window.movePage(), visibleItems: [pageInput, locationInput],
114 text: 'Enter page number to move then location to move it to' },
115 { value: 'Insert blank page', executeCommand: () => window.insertBlankPage(), visibleItems: [pageInput],
116 text: 'Enter a page number to add a blank page at (-1 to append at end)' },
117 { value: 'Split / extract pages', executeCommand: () => window.extractPages(), visibleItems: [pagesRange],
118 text: 'Enter a comma or dash separated list of page numbers to extract (leave empty for all pages)' },
119 { value: 'Crop pages', executeCommand: () => window.cropPage(), visibleItems: [pageInput, cropTop, cropBottom, cropLeft, cropRight, topLabel, bottomLabel, leftLabel, rightLabel],
120 text: 'Enter amount of pixels to crop, and then a page number (cropping cannot be undone)' },
121 { value: 'Merge documents', executeCommand: () => window.mergeDocuments(), visibleItems: [uploadButton, pagesRange, locationInput],
122 text: 'Upload document to merge then enter list of pages to insert (leave empty for all pages) and position to insert the pages at' },
123 ];
124 commandsArray.forEach(cmd => { // Create a UI element in the list for each command.
125 const option = document.createElement('option');
126 option.value = cmd.value;
127 option.textContent = cmd.value;
128 commandsList.appendChild(option);
129 });
130 const hideAllItems = () => [uploadButton, pagesRange, rotateAngle, pageInput, locationInput, cropTop, cropBottom, cropLeft, cropRight, topLabel, bottomLabel, leftLabel, rightLabel].forEach(item => item.style.display = 'none');
131 commandsList.onchange = (e) => {
132 const selectedIndex = e.target.selectedIndex;
133 promptLabel.textContent = commandsArray[selectedIndex].text;
134 hideAllItems();
135 commandsArray[selectedIndex].visibleItems.forEach(item => item.style.display = '');
136 }
137 commandsList.selectedIndex = 0;
138 promptLabel.textContent = commandsArray[0].text;
139 hideAllItems();
140 controlsContainer.id = 'controls-container';// Set the id for CSS styling.
141 element.insertBefore(controlsContainer, element.firstChild);
142
143 // Store elements globally for function access.
144 window.pagesRange = pagesRange;
145 window.rotateAngle = rotateAngle;
146 window.pageInput = pageInput;
147 window.locationInput = locationInput;
148 window.cropTop = cropTop;
149 window.cropBottom = cropBottom;
150 window.cropLeft = cropLeft;
151 window.cropRight = cropRight;
152 window.newDoc = newDoc;
153 }
154}
155
156window.UIElements = UIElements; // Expose the class to the global scope.
157
1/* Controls Container */
2.control-container {
3 align-items: center;
4 gap: 1px;
5 margin: 5px;
6 padding-bottom: 5px;
7 border-bottom: 1px solid #e0e0e0;
8 background-color: rgba(112, 198, 255, 0.2 /* Light blue background for contrast */);
9}
10
11.elements-style {
12 margin-right: 7px;
13 padding: 5px;
14 border: 1px solid #ccc;
15 border-radius: 4px;
16 font-size: 13px;
17 width: 70px;
18}
19
20.list-style {
21 margin-right: 7px;
22 padding: 5px;
23 border: 1px solid #ccc;
24 border-radius: 4px;
25 font-size: 13px;
26}
27
28/* General Button Styles */
29.btn {
30 background-color: #007bff;
31 margin: 0 10px;
32 padding: 5px 10px;
33 border: 1px solid #ccc;
34 border-radius: 4px;
35 cursor: pointer;
36 font-size: 14px;
37 transition: all 0.2s ease;
38 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
39 color: white;
40}
41
42.btn:hover {
43 background-color: #0056b3;
44 transform: translateY(-1px);
45 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
46}
47
48.btn:active {
49 transform: translateY(1px);
50 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
51}
52
53.btn:disabled {
54 background-color: #ccc;
55 cursor: not-allowed;
56 box-shadow: none;
57}
58
59/* Responsive Design */
60@media (max-width: 768px) {
61 .btn {
62 width: 100%;
63 margin: 5px 0;
64 }
65}
66
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales