Build a PWA Document Scanner with JavaScript: Capture, Edit and Upload to PDF

In many industries, a document scanner app is essential for capturing, editing, and uploading documents such as invoices and receipts to the cloud. By leveraging the Dynamsoft Document Viewer SDK, you can build a Progressive Web App (PWA) document scanner that enables users to capture images, crop them, combine multiple pages into a single document, and convert the scanned documents to PDF format for easy sharing and storage. This tutorial will guide you through the process of creating a PWA document scanner using the Dynamsoft Document Viewer SDK.

What you’ll build: A Progressive Web App that captures documents from the device camera, lets you crop and edit scanned pages, combines them into a multi-page PDF, and uploads the result to a Node.js server — all running in the browser with no native app install.

Key Takeaways

  • Dynamsoft Document Viewer SDK provides a complete JavaScript API for camera capture, page cropping, annotation, and PDF export inside any modern browser.
  • Wrapping a web document scanner as a PWA (with manifest.json and a service worker) gives users an installable, offline-capable scanning experience on mobile and desktop.
  • The saveToPdf() method converts captured pages — including annotations — into a single PDF blob that can be uploaded as a Base64 payload to any HTTP endpoint.
  • This approach eliminates native-app distribution overhead while still accessing the device camera via the browser’s MediaDevices API.

Common Developer Questions

  • How do I build a PWA document scanner with JavaScript?
  • How do I capture documents from a phone camera and convert them to PDF in the browser?
  • How do I upload scanned PDF files from a web app to a Node.js server?

PWA Document Scanner Demo Video

Step 1: Set Up Prerequisites

  • Dynamsoft Document Viewer: This package provides JavaScript APIs for viewing and annotating a wide range of document formats, including PDFs and images like JPEG, PNG, TIFF, and BMP. Key features include PDF rendering, page navigation, image quality enhancement, and document saving capabilities. You can find the SDK on npm.

  • Dynamsoft Capture Vision Trial License: A 30-day free trial license that provides access to all features of the Dynamsoft SDKs.

Step 2: Create a Node.js Server for Receiving PDF Uploads

Let’s create a Node.js/Express server to receive a Base64 string and save it as a PDF file to the local disk.

Install Server Dependencies

  1. Create a folder for your server:

     mkdir server
     cd server
    
  2. Initialize a Node.js project:

     npm init -y
    
  3. Install Express and cors:

     npm install express cors
    

    Explanation

    • Express simplifies the creation of a web server.
    • CORS (Cross-Origin Resource Sharing) is middleware that allows cross-origin requests.

Write the Express Upload Endpoint

  1. Create an index.js file with the following code:

     const express = require('express');
     const cors = require('cors');
     const fs = require('fs');
     const path = require('path');
        
     const app = express();
     const PORT = 3000;
        
     app.use(cors());
     app.use(express.json({ limit: '10mb' }));
        
     app.post('/upload', (req, res) => {
         const { image } = req.body;
        
         if (!image) {
             return res.status(400).json({ error: 'No image provided.' });
         }
        
         const buffer = Buffer.from(image, 'base64');
        
         // Save the image to disk
         const filename = `image_${Date.now()}.pdf`;
         const filepath = path.join(__dirname, 'uploads', filename);
        
         // Ensure the uploads directory exists
         if (!fs.existsSync('uploads')) {
             fs.mkdirSync('uploads');
         }
        
         fs.writeFile(filepath, buffer, (err) => {
             if (err) {
                 console.error('Failed to save image:', err);
                 return res.status(500).json({ error: 'Failed to save image.' });
             }
        
             console.log('Image saved:', filename);
             res.json({ message: 'Image uploaded successfully!', filename });
         });
     });
        
     // Start the server
     app.listen(PORT, () => {
         console.log(`Server is running on http://localhost:${PORT}`);
     });
    
  2. Run the web server:

     node index.js
    

Step 3: Implement a PWA Document Scanner with Dynamsoft Document Viewer

To get started with the Dynamsoft Document Viewer, download the official sample code from the GitHub repository: https://github.com/Dynamsoft/mobile-web-capture/tree/master/samples/complete-document-capturing-workflow. This sample demonstrates how to capture, crop, and combine multiple images into a single document using the Dynamsoft Document Viewer SDK.

Based on the project, we will add the following features:

  • Support for PWA.
  • Upload scanned documents as PDF files to a server.

Configure the PWA Manifest and Service Worker

  1. Create a folder for your PWA project:

     mkdir client
     cd client
    
  2. Copy the sample code into the client folder.
  3. Create a manifest.json file in the root directory of your project with the following content:

     {
         "short_name": "MyPWA",
         "name": "My Progressive Web App",
         "icons": [
             {
                 "src": "icon.png",
                 "sizes": "192x192",
                 "type": "image/png"
             }
         ],
         "start_url": "/",
         "display": "standalone",
         "background_color": "#ffffff",
         "theme_color": "#000000"
     }
    
  4. Create a sw.js file in the root directory of your project with the following content:

     const CACHE_NAME = 'pwa-cache-v1';
     const urlsToCache = [
         '/',
         '/index.css',
         '/manifest.json',
     ];
        
     self.addEventListener('install', (event) => {
         event.waitUntil(
             caches.open(CACHE_NAME).then((cache) => {
                 console.log('Opened cache');
                 return cache.addAll(urlsToCache);
             })
         );
     });
        
     self.addEventListener('fetch', (event) => {
         event.respondWith(
             caches.match(event.request).then((response) => {
                 return response || fetch(event.request);
             })
         );
     });
    
  5. Register the service worker in the index.html file:

     <script>
         if ('serviceWorker' in navigator) {
             window.addEventListener('load', () => {
                 navigator.serviceWorker
                     .register('/sw.js')
                     .then((registration) => {
                         console.log('Service Worker registered with scope:', registration.scope);
                     })
                     .catch((error) => {
                         console.error('Service Worker registration failed:', error);
                     });
             });
         }
     </script>
    

Save and Upload Scanned Documents as PDF

  1. In uiConfig.js, add a customized download button with a click event named save:

     {
         type: Dynamsoft.DDV.Elements.Button,
         className: "ddv-button ddv-button-download",
         events: {
             click: "save",
         }
     }
    
  2. In index.html, implement the save event. After saving the document as a PDF, convert the blob to a Base64 string and upload it to the server:

     async function uploadImage(base64String) {
         return fetch('http://localhost:3000/upload', {
             method: 'POST',
             headers: {
                 'Content-Type': 'application/json',
             },
             body: JSON.stringify({ image: base64String }),
         });
     }
    
     editViewer.on("save", async () => {
         // https://www.dynamsoft.com/document-viewer/docs/api/interface/idocument/index.html#savetopdf
         const pdfSettings = {
             saveAnnotation: "annotation",
         };
    
         let blob = await editViewer.currentDocument.saveToPdf(pdfSettings);
    
         // convert blob to base64
         let reader = new FileReader();
         reader.readAsDataURL(blob);
         reader.onloadend = async function () {
             let base64data = reader.result;
    
             try {
                 const response = await uploadImage(base64data.split(',')[1]);
    
                 if (response.ok) {
                     alert('Upload successful!');
                 } else {
                     alert('Upload failed.');
                 }
             } catch (error) {
                 console.error(error);
    
             }
    
         }
    
     });
    

Step 4: Run and Test the PWA Document Scanner

  1. Start a web server in the root directory of your project:

     python -m http.server
    
  2. Visit http://localhost:8000 in your web browser.

    PWA Document Scanner

Common Issues and Edge Cases

  • Camera access denied on mobile browsers: Ensure the page is served over HTTPS (or localhost). The browser’s MediaDevices API requires a secure context; without it, getUserMedia will fail silently or throw a NotAllowedError.
  • Service worker caching stale assets after an SDK update: Increment the CACHE_NAME version string (e.g., pwa-cache-v2) in sw.js whenever you update SDK files; otherwise the browser will serve outdated cached scripts.
  • PDF upload fails for large multi-page documents: The Express server above limits the JSON body to 10 MB (express.json({ limit: '10mb' })). For documents with many high-resolution pages, increase this limit or switch to a multipart/form-data upload with streaming.

Source Code

https://github.com/yushulx/web-twain-document-scan-management/tree/main/examples/pwa