Document Assembly Showcase Demo Code Sample

Effortlessly assemble PDF documents by dragging and dropping thumbnail pages between document viewers. Perform manipulation securely in the memory of the browser without any server-side dependencies.


This demo allows you to:

  • Upload your own PDF files
  • Move thumbnail pages between viewers and merge them
  • Edit the layout and content
  • Download the merged PDF file

Implementation steps
To add PDF assembly capability from two viewers:

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 Document Assembly demo

1// ES6 Compliant Syntax
2// GitHub Copilot - Model: GPT-4o
3// Date: July 14, 2025
4// File: index.js
5
6import WebViewer from '@pdftron/webviewer';
7
8const licenseKey = 'YOUR_LICENSE_KEY';
9
10// List of viewers with their configurations
11// Each viewer can have its own document, alignment, size, instance, and merging features
12let viewers = [
13 {
14 initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf',
15 style: {
16 alignment: 'Left',
17 width: '49%',
18 height: '100vh',
19 margin: '0 auto',
20 },
21 instance: null,
22 hasMerging: false, // The merging feature allows merging pages from one viewer to another
23 },
24 {
25 initialDoc: 'https://pdftron.s3.amazonaws.com/downloads/pl/report.docx',
26 style: {
27 alignment: 'Right',
28 width: '49%',
29 height: '100vh',
30 margin: '0 auto',
31 },
32 instance: null,
33 hasMerging: true,
34 },
35];
36
37// Customize the WebViewer UI
38// Add custom buttons for merging (if merging feature is enabled) and downloading PDFs
39function customizeUI(viewer) {
40 const { UI } = viewer.instance;
41
42 // Merge button
43 let mergeButton = null;
44 if (viewer.hasMerging) {
45 mergeButton = new UI.Components.CustomButton({
46 dataElement: 'mergeButton',
47 className: 'custom-button-class',
48 label: 'Merge Page',
49 title: 'Merge the left WebViewer\'s first page, to be inserted prior to the right WebViewer\'s first page.',
50 onClick: () => mergePage(), // Merge the pages
51 style: {
52 padding: '10px 20px',
53 backgroundColor: 'white',
54 color: 'blue',
55 border: '1px solid blue',
56 }
57 });
58 }
59
60 // Download Pdf button
61 let downloadButton = new UI.Components.CustomButton({
62 dataElement: 'downloadPdfButton',
63 className: 'custom-button-class',
64 label: 'Download as PDF',
65 onClick: () => downloadPdf(viewer.instance), // Download the PDF
66 style: {
67 padding: '10px 20px',
68 backgroundColor: 'blue',
69 color: 'white',
70 }
71 });
72
73 let defaultHeader = UI.getModularHeader('default-top-header');
74
75 // If the viewer has merging enabled, add the merge button and download button to the header
76 // Otherwise, just add the download button
77 if (viewer.hasMerging)
78 defaultHeader.setItems([...defaultHeader.items, mergeButton, downloadButton]);
79 else
80 defaultHeader.setItems([...defaultHeader.items, downloadButton]);
81};
82
83// Merge the first page from the left viewer to be located at position #1 of the right viewer
84// This function will be called when the merge button is clicked
85// This function retrieves the source document, exports annotations, and inserts page(s) into the destination document
86const mergePage = async () => {
87
88 const srcDoc = await viewers[0].instance.Core.documentViewer.getDocument();
89 const dstDoc = await viewers[1].instance.Core.documentViewer.getDocument();
90
91 // get first page as a blob
92 const xfdfString = await viewers[0].instance.Core.annotationManager.exportAnnotations();
93 const data = await srcDoc.getFileData({
94 xfdfString,
95 });
96 const arr = new Uint8Array(data);
97 const blob = new Blob([arr], { type: 'application/pdf' });
98
99 const docToInsert = await viewers[1].instance.Core.createDocument(blob, { extension: 'pdf', l: licenseKey });
100
101 dstDoc.insertPages(docToInsert, [1], 1);
102};
103
104// Download the PDF
105const downloadPdf = async (instance) => {
106
107 // Get the filename from the document
108 let filename = instance.Core.documentViewer.getDocument().getFilename();
109
110 // Ensure it ends with .pdf. If not, replace it with .pdf extension
111 if (!filename.endsWith('.pdf'))
112 filename = filename.replace(/\.[^/.]+$/, '') + '.pdf';
113
114 // Set the options for downloading the PDF
115 const options = {
116 filename: filename,
117 flags: instance.Core.SaveOptions.LINEARIZED,
118 downloadType: 'pdf'
119 };
120
121 instance.UI.downloadPdf(options);
122};
123
124// Create and initialize the WebViewer instances with their configurations,
125// as many viewers are defined in the viewers list
126function createWebViewer(viewer) {
127 const element = document.createElement('div');
128 element.id = `WebViewer${viewer.style.alignment}`;
129 element.style.width = viewer.style.width;
130 element.style.height = viewer.style.height;
131 element.style.margin = viewer.style.margin;
132 element.style.float = viewer.style.alignment;
133
134 //find 'viewer' element in the body and append the viewer element to it
135 const viewerElement = document.getElementById('viewer');
136 if (!viewerElement) {
137 console.error('Viewer element not found! Make sure the template execution area is ready.');
138 return;
139 }
140 viewerElement.appendChild(element);
141
142 WebViewer(
143 {
144 path: '/lib',
145 initialDoc: viewer.initialDoc,
146 loadAsPDF: true,
147 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
148 licenseKey: licenseKey,
149 },
150 element
151 ).then(instance => {
152
153 // Enable merging pages feature from another WebViewer
154 instance.UI.enableFeatures(instance.UI.Feature.MultipleViewerMerging);
155
156 instance.Core.documentViewer.addEventListener('documentLoaded', () => {
157 // Open the thumbnails panel
158 instance.UI.openElements(['thumbnailsPanel']);
159
160 // Select the first page in the thumbnails panel
161 setTimeout(() => {
162 instance.UI.ThumbnailsPanel.selectPages([1]);
163 }, 500);
164
165 console.log(`✅ ${element.id} loaded successfully.`);
166
167 // Store the instance reference in the viewer object
168 viewer.instance = instance;
169
170 // customize WebViewer UI
171 customizeUI(viewer);
172 });
173 }).catch((error) => {
174 console.error(`❌ Failed to initialize ${element.id}:`, error);
175 });
176}
177
178// Create the defined viewers in the viewers list
179viewers.forEach(viewer => {
180 createWebViewer(viewer);
181});

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales