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

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales