> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/get-started/samples/showcase-demos-document-classification.md).

# Document Classification Showcase Demo Code Sample

Analize a document and produce a JSON report to identify the document type. Supports multi page documents.

{% hint style="info" %}
**Requirements**

*These packages are required to use these features in production. Trial keys have unlimited access to all features*

<a href="/web/get-started/readme.md" class="button primary">Web SDK</a><a href="/core/get-started/get-started.md" class="button primary">Server SDK</a><a href="https://apryse.com/capabilities#SmartDataExtraction" class="button primary">Package: Smart Data Extraction</a><a href="https://showcase.apryse.com/document-classification" class="button primary">Live demo</a>
{% endhint %}

Classify documents with custom-trained AI. This feature uses a specialized AI model to analyze documents and return a JSON output that identifies the document type. It supports multipage documents and is trained on 19 categories, primarily in English:

* "advertisement"
* "budget"
* "email"
* "file\_folder"
* "form"
* "handwritten"
* "id"
* "invoice"
* "letter"
* "memo"
* "news\_article"
* "passport"
* "presentation"
* "questionnaire"
* "receipt"
* "resume"
* "scientific\_publication"
* "scientific\_report"
* "specification"

This demo allows you to:

* Upload your own PDF file
* Perform classification of the document
* Produce a JSON report

**Implementation steps** To add Document Classification capability in WebViewer:

Step 1: Choose your [preferred web stack](/web/get-started/readme.md). 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-license-key/apryse-license-key platform="WEB\_VIEWER" variant="compact" %}

{% tabs %}
{% tab title="index.js" %}

<pre class="language-js" data-line-numbers><code class="lang-js">// ES6 Compliant Syntax
// GitHub Copilot - October 22, 2025
// File name: document-classification/client/index.js

const licenseKey = '<code class="expression">visitor.claims.wvKey || "YOUR_WEBVIEWER_LICENSE_KEY"</code>';

// Global variables
const element = document.getElementById('viewer');
let instance = null;

// Initialize WebViewer
WebViewer({
  path: '/lib',
  initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/document_classification_pack.pdf',
  enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
  licenseKey: licenseKey,
}, element).then((inst) => {
  instance = inst;
  instance.Core.documentViewer.addEventListener('documentLoaded', () => {
    resultArea.textContent = "📄 Document loaded. You can now press the 'Classify Document' button to classify it.";
  });
});

// Perform classification by sending the current PDF page to the server
const classifyDoc = async () => {
  instance.UI.openElements(['loadingModal']);
  resultArea.textContent = '⏳ Classifying document, please wait...';
  let resultText = '';
  // Preparation of the PDF blob to be sent to the server
  const doc = instance.Core.documentViewer.getDocument();
//  const currentPage = instance.Core.documentViewer.getCurrentPage();
  const xfdfString = await instance.Core.annotationManager.exportAnnotations(); // obtaining annotations in the loaded document
  const data = await doc.getFileData({ xfdfString });
  const arr = new Uint8Array(data);
  const blob = new Blob([arr], { type: 'application/pdf' });
  const formData = new FormData();
  formData.append(doc.filename, blob, doc.filename);
  // Send the PDF blob to the server for processing
  new Promise(function (resolve, reject) {
    console.log('Sending PDF to server for processing...');
    fetch(`http://localhost:5050/server/handler.js?filename=${doc.filename}`, {
      method: 'POST',
      body: formData,
    }).then(function (response) {

      if (response.status === 200) {
        response.text().then(function (json) {
          resultText = json;
          resolve();
        })
      } else {
        const errorText = `Server responded with status: ${response.status}`;
        resultText = errorText + resultText;
        console.error(resultText);
        reject(new Error(`Server error: ${response.status}`));
      }
    }).catch(function (error) {
      let errorText = 'Failed to connect to server: ' + error;
      errorText += '\n Attempted URL: http://localhost:5050/server/handler.js';
      errorText += '\n This likely means the Classification server is not running on port 5050';
      console.error(errorText);
      resultText = errorText + resultText;
      reject(error);
    });
  }).catch(function (error) {
    const errorText = ' Error in PDF upload promise: ' + error;
    console.error(errorText);
    resultText = errorText + resultText;
  }).finally(function () {
    resultArea.textContent = resultText;
    instance.UI.closeElements(['loadingModal']);
  });
}

// UI section

// Create a container for the controls
const controlsContainer = document.createElement('div');

// Create 2 divs inside the container for left and right sections
const leftDiv = document.createElement('div');
const rightDiv = document.createElement('div');
leftDiv.className = 'vertical-container left-panel'; // side-by-side divs using (display: inline-block) and (vertical-align: top)
rightDiv.className = 'vertical-container right-panel';
controlsContainer.appendChild(leftDiv);
controlsContainer.appendChild(rightDiv);

// Add description text to the left div
const description = document.createElement('p');
description.textContent = "A demo of Apryse Server SDK's Document Classification, powered by custom trained AI. The document will be analyzed and a resulting JSON will identify the document type. Supports multi page documents.";
leftDiv.appendChild(description);
leftDiv.appendChild(document.createElement('br'));

// Add classify document button to the left div
const classifyDocButton = document.createElement('button');
classifyDocButton.className = 'btn';
classifyDocButton.textContent = 'Classify Document';
classifyDocButton.onclick = async () => {
  await classifyDoc();
};
leftDiv.appendChild(classifyDocButton);

leftDiv.appendChild(document.createElement('br'));
leftDiv.appendChild(document.createElement('br'));

const note = document.createElement('p');
note.innerHTML = "&#x3C;b>Note: only the first 2 pages will be processed&#x3C;/b>.";
leftDiv.appendChild(note);

const resultArea = document.createElement('textarea');
resultArea.className = 'result-area';
resultArea.readOnly = true;
resultArea.textContent = "Classification result will appear here.";
rightDiv.appendChild(resultArea);

element.insertBefore(controlsContainer, element.firstChild);

</code></pre>

{% endtab %}

{% tab title="index.css" %}
{% code title="index.css" lineNumbers="true" %}

```css
/* side-by-side divs */
.vertical-container {
    display: inline-block;
    vertical-align: top;
}

/* Button Styles */
.btn {
    background-color: #007bff;
    margin: 0 10px;
    padding: 5px 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    transition: all 0.2s ease;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    color: white;
}

.btn:hover {
    background-color: #0056b3;
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

.btn:active {
    transform: translateY(1px);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

.btn:disabled {
    background-color: #ccc;
    cursor: not-allowed;
    box-shadow: none;
}

/* Layout Styles */
.left-panel {
    width: 40%;
}

.right-panel {
    width: 60%; /* right div is wider to accommodate JSON data display */
}

.result-area {
    width: 100%;
    height: 150px;
    white-space: pre-wrap;
    font-family: 'Courier New', monospace;
    font-size: 12px;
    overflow: auto;
    background-color: gray;
    color: white;
}

/* Responsive Design */
@media (max-width: 768px) {
    .btn {
        width: 100%;
        margin: 5px 0;
    }
}

```

{% endcode %}
{% endtab %}

{% tab title="handler.js" %}

<pre class="language-js" data-line-numbers><code class="lang-js">const { PDFNet } = require('@pdftron/pdfnet-node');
const path = require('path');
const fs = require('fs');

// **Important** 
// You must get a license key from Apryse for the server to run. 
// A trial key can be obtained from:
// https://docs.apryse.com/core/guides/get-started/trial-key 
const licenseKey = '<code class="expression">visitor.claims.wvKey || "YOUR_SERVER_LICENSE_KEY"</code>';
const multer = require('multer');
const { response } = require('express');
const upload = multer();
const serverFolder = 'server';
const sentDocuments = 'sentDocuments';
const serverHandler = `/${serverFolder}/handler.js`;

// Initialize PDFNet once when the module loads
let isInitialized = false;
let initializationError = null;

async function initializeServer() {
  try {
    console.log('Initializing PDFNet Data Extraction module...');
    
    // Check if DataExtractionModule is available
    if (!PDFNet.DataExtractionModule) {
      throw new Error('DataExtractionModule not available');
    }
    
    // Check if document classification is available
    const isAvailable = await PDFNet.DataExtractionModule.isModuleAvailable(
      PDFNet.DataExtractionModule.DataExtractionEngine.e_DocClassification);
    if (!isAvailable) {
      throw new Error('Document Classification module not available');
    }
    
    isInitialized = true;
  } catch (error) {
    console.error('❌ Error initializing Data Extraction module:', error.message);
    initializationError = error;
  }
}

module.exports = async (app) => {

  // Handle POST request sent to '/server/handler.js'
  // This endpoint receives the currently loaded PDF file in the Apryse webviewer and sends
  // its classification back to the client as JSON data
  app.post(serverHandler, upload.any(), async (request, response) => {
    try {
      // Check if PDFNet is initialized
      if (!isInitialized) {
        return response.status(503).json({ 
          error: 'Server not properly initialized', 
          details: initializationError?.message 
        });
      }

      if (!fs.existsSync(sentDocuments))
        fs.mkdirSync(sentDocuments);
      
      const sentPdf = path.resolve(__dirname, `./${sentDocuments.split('/').pop()}/${request.query.filename}`);
      fs.writeFileSync(sentPdf, request.files[0].buffer);
      
      response.header('Content-Type', 'application/json');
      
      const opts = new PDFNet.DataExtractionModule.DataExtractionOptions(); 
      opts.setPages('1-2'); // process 2 pages
      console.log(opts);
      const json = await PDFNet.DataExtractionModule.extractDataAsString(
        sentPdf, PDFNet.DataExtractionModule.DataExtractionEngine.e_DocClassification, opts);
      
      await fs.promises.unlink(sentPdf); // delete the document after processing
      response.status(200).send(json);
    } catch (e) {
      console.error('Error processing document:', e);
      response.status(500).json({ 
        error: `Error extracting JSON text from PDF file ${request.query.filename}`,
        details: e.message 
      });
    }
  });
};

// Initialize PDFNet when module loads
PDFNet.runWithoutCleanup(initializeServer, licenseKey).catch(error => {
  console.error('❌ Fatal error initializing PDFNet:', error);
  initializationError = error;
});
</code></pre>

{% endtab %}

{% tab title="server.js" %}
{% code title="server.js" lineNumbers="true" %}

```js

const express = require('express');
const fs = require('fs');
const bodyParser = require('body-parser');
const open = (...args) => import('open').then(({ default: open }) => open(...args));
const handler = require('./handler.js');
const port = process.env.PORT || 5050;
const app = express();
const sentPdfs = 'sentPdfs';

// CORS middleware to allow cross-origin requests from the playground
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  
  // Handle preflight OPTIONS requests
  if (req.method === 'OPTIONS') {
    res.sendStatus(200);
  } else {
    next();
  }
});

app.use(bodyParser.text());
app.use('/client', express.static('../client')); // For statically serving 'client' folder at '/'

handler(app);

// Run server
const server = app.listen(port, 'localhost', (err) => {
  if (err) {
    console.error(err);
  } else {
    console.info(`Server is listening at http://localhost:${port}`);
    
  }
});

// Server shutdown and cleanup
function shutdown() {
  console.log('Cleanup started...');

  // Example: Close server
  server.close(() => {
    console.log('Server closed.');

    // Removes sent PDFs folder
    if (fs.existsSync(sentPdfs))
      fs.rmdirSync(sentPdfs, { recursive: true });

    // If no async cleanup, exit directly
    process.exit(0);
  });
}

// Handle shutdown signals
process.on('SIGINT', shutdown);   // Ctrl+C
process.on('SIGTERM', shutdown);  // kill command or Docker stop
process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
  shutdown();
});
```

{% endcode %}
{% endtab %}

{% tab title="package.json" %}
{% code title="package.json" lineNumbers="true" %}

```json
{
  "name": "document-classification-server",
  "version": "1.0.0",
  "description": "Document Classification Demo Server Component",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "node server.js"
  },
  "dependencies": {
    "@pdftron/data-extraction": "^11.8.0",
    "@pdftron/pdfnet-node": "^11.8.0",
    "body-parser": "^1.20.2",
    "express": "^4.18.2",
    "multer": "^1.4.4",
    "open": "^9.1.0"
  },
  "keywords": [
    "document-classification",
    "pdf",
    "server",
    "pdftron",
    "webviewer"
  ],
  "author": "Apryse",
  "license": "MIT"
}

```

{% endcode %}
{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/get-started/samples/showcase-demos-document-classification.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
