Key-Value Extraction Showcase Demo Code Sample

Requirements
View Demo

Quickly extract key-value pairs from PDFs, convert values into JSON for easy analysis, and display annotations that highlight each paired element.

This demo allows you to:

  • Upload your own PDF file or test on sample files
  • Extract a JSON containing the key-value elements in the PDF
  • Create colorized key-value annotations for extracted paired elements

Implementation steps

To add key-value extraction capability with WebViewer:

Step 1: Choose your preferred web stack for WebViewer
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// ES6 Compliant Syntax
2// GitHub Copilot v1.0, Claude Sonnet 4, November 13, 2025
3// File: index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7// Key-Value Extraction Demo
8// This code demonstrates how to extract key-value pairs from documents using WebViewer
9//
10// **Important**
11// 1. You must get a license key from Apryse for the server to run.
12// A trial key can be obtained from:
13// https://docs.apryse.com/core/guides/get-started/trial-key
14//
15// 2. You need to also run the `npm install` command at /key-value-extraction/server/ location to install the `@pdftron/pdfnet-node` and `@pdftron/data-extraction` packages.
16
17function initializeWebViewer() {
18
19 // This code initializes the WebViewer with the basic settings
20 WebViewer({
21 path: '/lib',
22 licenseKey: 'YOUR_LICENSE_KEY',
23 enableFilePicker: true,
24 }, document.getElementById('viewer')).then((instance) => {
25
26 // Add the demo-specific functionality
27 customizeUI(instance).then(() => {
28 // Create UI controls after demo is initialized
29 UIElements.createUIControls(instance);
30 });
31 });
32}
33
34// Starting page for extraction
35let startPage = 1;
36
37// Global variable to hold result data
38window.resultData = null;
39
40// Custom File class to hold file metadata (not to be confused with browser's File API)
41class FileMetadata {
42 constructor(options) {
43 this.name = options.name;
44 this.displayName = options.displayName;
45 this.path = options.path;
46 this.extension = options.extension;
47 this.displayExtension = options.displayExtension;
48 this.id = options.id;
49 }
50}
51
52const files = {
53 DRIVERS_LICENSE: new FileMetadata({
54 name: 'sample-license.pdf',
55 displayName: 'Driver\'s License',
56 path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/sample-license.pdf',
57 extension: 'pdf',
58 id: 100
59 }),
60 SALES_INVOICE: new FileMetadata({
61 name: 'sales-invoice.pdf',
62 displayName: 'Sales Invoice',
63 path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/sales-invoice.pdf',
64 extension: 'pdf',
65 id: 14
66 })
67}
68
69const sampleDocuments = [
70 files.DRIVERS_LICENSE,
71 files.SALES_INVOICE
72];
73window.sampleDocuments = sampleDocuments;
74
75const defaultFile = sampleDocuments[1].path; // SALES_INVOICE
76
77const customizeUI = async (instance) => {
78 // Customize the UI for the key value extraction demo
79 instance.UI.setToolbarGroup('toolbarGroup-View');
80 instance.UI.disableElements(['thumbnailControl']);
81
82 // Reset variables when new document is loaded
83 instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
84 window.resultData = null;
85 startPage = 1;
86
87 // Reset the JSON display area and Color Legend
88 UIElements.resetUI(instance);
89 });
90
91 // Load the default file for demonstration
92 if (defaultFile) {
93 instance.UI.loadDocument(defaultFile);
94 }
95};
96
97// Function to extract key-value pairs from title block via server
98const extractKeyValuePairs = async (instance) => {
99 const doc = instance.Core.documentViewer.getDocument();
100 if (doc) {
101 const pdfBuffer = await doc.getFileData({ flags: instance.Core.SaveOptions.LINEARIZED });
102 console.log('Sending PDF to server for key-value extraction...');
103 const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' });
104 const formData = new FormData();
105 formData.append('pdffile', pdfBlob, 'viewerDocument.pdf');
106
107 // Send the PDF to the server to extract key-value pairs
108 const postResponse = await fetch('http://localhost:5050/server/handler.js/extract-key-value-pairs', {
109 method: 'POST',
110 body: formData,
111 });
112
113 if (postResponse.status !== 200) {
114 throw new Error(`Server error during PDF upload: ${postResponse.status}`);
115 }
116
117 // Retrieve and parse the JSON response
118 const jsonResponse = await postResponse.json();
119 const docStructureData = JSON.parse(jsonResponse);
120 resultData = JSON.stringify(docStructureData, null, 2);
121
122 // Draw annotations on the document based on extracted data
123 drawAnnotations(docStructureData, instance);
124 return;
125 }
126}
127
128window.extractKeyValuePairs = extractKeyValuePairs; // Make extractKeyValuePairs globally available so that the UIElements module can access it
129
130// Function to draw annotations on the document based on extracted key-value data
131const drawAnnotations = (docStructureData, instance) => {
132 const { annotationManager, Annotations } = instance.Core;
133
134 // Retrieve the first page's data
135 const page = docStructureData.pages[startPage - 1];
136 const pageNumber = page?.properties?.pageNumber;
137 console.log(`Processing Page ${pageNumber} for annotations...`);
138 for (const kv of page.keyValueElements ?? []) {
139 const valueRect = kv?.rect;
140 const keyRect = kv?.key?.rect;
141 const hasValueWords = (kv?.words?.length ?? 0) > 0;
142
143 // Only draw if value has words
144 if (!hasValueWords) {
145 console.log('Skipping annotation for key-value pair with no value words.');
146 continue;
147 }
148 // value: blue
149 const valueAnnot = new Annotations.RectangleAnnotation({
150 PageNumber: pageNumber,
151 X: valueRect[0],
152 Y: valueRect[1],
153 Width: valueRect[2] - valueRect[0],
154 Height: valueRect[3] - valueRect[1],
155 StrokeColor: new Annotations.Color(0, 0, 255),
156 StrokeThickness: 1,
157 });
158 annotationManager.addAnnotation(valueAnnot);
159 annotationManager.redrawAnnotation(valueAnnot);
160
161 // key: red
162 const keyAnnot = new Annotations.RectangleAnnotation({
163 PageNumber: pageNumber,
164 X: keyRect[0],
165 Y: keyRect[1],
166 Width: keyRect[2] - keyRect[0],
167 Height: keyRect[3] - keyRect[1],
168 StrokeColor: new Annotations.Color(255, 0, 0),
169 StrokeThickness: 1,
170 });
171 annotationManager.addAnnotation(keyAnnot);
172 annotationManager.redrawAnnotation(keyAnnot);
173
174 // Green connector
175 const line = new Annotations.LineAnnotation();
176 line.pageNumber = pageNumber;
177 line.StrokeColor = new Annotations.Color(0, 255, 0);
178 line.StrokeThickness = 1;
179 line.Start = topLeftPoint(valueRect, instance);
180 line.End = topLeftPoint(keyRect, instance);
181 annotationManager.addAnnotation(line);
182 annotationManager.redrawAnnotation(line);
183 }
184};
185
186// Helper function to get top-left point of a rectangle
187const topLeftPoint = ([x1, y1, x2, y2], instance) => {
188 return new instance.Core.Math.Point(Math.min(x1, x2), Math.min(y1, y2));
189};
190
191// Cleanup function for when the demo is closed or page is unloaded
192const cleanup = (instance) => {
193 if (typeof instance !== 'undefined' && instance.UI) {
194 if (instance.Core.documentViewer.getDocument()) {
195 // Insert any other cleanup code here
196 }
197 console.log('Cleaning up title-block-data-extraction demo');
198 }
199};
200
201// Register cleanup for page unload
202window.addEventListener('beforeunload', () => cleanup(instance));
203window.addEventListener('unload', () => cleanup(instance));
204
205// Helper function to load the ui-elements.js script
206function loadUIElementsScript() {
207 return new Promise((resolve, reject) => {
208 if (window.UIElements) {
209 console.log('UIElements already loaded');
210 resolve();
211 return;
212 }
213 const script = document.createElement('script');
214 script.src = '/showcase-demos/key-value-extraction/client/ui-elements.js';
215 script.onload = function () {
216 console.log('✅ UIElements script loaded successfully');
217 resolve();
218 };
219 script.onerror = function () {
220 console.error('Failed to load UIElements script');
221 reject(new Error('Failed to load ui-elements.js'));
222 };
223 document.head.appendChild(script);
224 });
225}
226
227// Load UIElements script first, then initialize WebViewer
228loadUIElementsScript().then(() => {
229 initializeWebViewer();
230}).catch((error) => {
231 console.error('Failed to load UIElements:', error);
232});
233

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales