In this tutorial, you’ll learn how to create a browser-based barcode scanner that can parse the data encoded in the AAMVA-standardized PDF417 barcodes found on US driver’s licenses. The result will be a single stand-alone HTML file you can run on your server.

To achieve this, we’ll use plain JavaScript and the Scanbot Web Barcode Scanner SDK. We’re going to follow these steps:
- Setting up the barcode scanner
- Optimizing the scanner for PDF417 barcodes
- Implementing the AAMVA parser
Let’s get started!
Step 1: Set up the barcode scanner
In your project folder, create an index.html with some boilerplate code.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
<title>AAMVA Barcode Parser</title>
</head>
<body>
</body>
</html>
To set up our barcode scanner, we’ll have to do the following:
- Create a button that calls up the scanning interface when clicked.
- Include a
<p>
element on the page for displaying the scanning result. - Import the Scanbot Web SDK using a CDN.
- Process the scan result before displaying it on the page.
⚠️ In this tutorial, we’re importing the SDK via jsDelivr. However, you should only do this for quick prototyping. In your production environment, please download the Web SDK directly (or install it via npm) and include its files in your project.
Your index.html will look something like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
<title>AAMVA Barcode Parser</title>
</head>
<body style="margin: 0">
<button id="start-scanning">Start scanning</button>
<p id="result"></p>
<script type="module">
import "https://cdn.jsdelivr.net/npm/scanbot-web-sdk@7.0.0/bundle/ScanbotSDK.ui2.min.js";
const sdk = await ScanbotSDK.initialize({
enginePath:
"https://cdn.jsdelivr.net/npm/scanbot-web-sdk@7.0.0/bundle/bin/barcode-scanner/",
});
document
.getElementById("start-scanning")
.addEventListener("click", async () => {
const config =
new ScanbotSDK.UI.Config.BarcodeScannerScreenConfiguration();
const scanResult = await ScanbotSDK.UI.createBarcodeScanner(config);
if (scanResult?.items?.length > 0) {
document.getElementById("result").innerText =
`Barcode type: ${scanResult.items[0].barcode.format} \n` +
`Barcode content: "${scanResult.items[0].barcode.text}" \n`;
} else {
document.getElementById("result").innerText = "Scanning aborted by the user";
}
});
</script>
</body>
</html>
This already provides us with a fully functional barcode scanner – although we still have to adjust it a bit to make it better suited for the AAMVA standard. Feel free to run the app and test its functionalities.

💡 To test your web app on your phone, you have a few options. One option is to use a service like ngrok, which creates a tunnel to one of their SSL-certified domains. Their Quick Start guide will help you get up and running quickly.
You’ll notice that scanning an AAMVA barcode outputs the encoded data, but in a format that’s hard to read. We’ll change that in step 3. But first, let’s adjust our scanner’s configuration.
Step 2: Optimize the scanner for PDF417 barcodes
As it is now, our scanner will read any barcode type. Restricting it to scanning only PDF417 codes (the barcode type used by the AAMVA standard) prevents unintended scans and improves the scanner’s performance, as it doesn’t need to check each supported symbology.
So let’s implement this in our code by modifying the BarcodeScannerScreenConfiguration
we assigned to the config
constant in the previous step.
config.scannerConfiguration.barcodeFormats = ["PDF_417"];
Let’s also change the viewfinder’s aspect ratio. It’s square by default, but since PDF417 barcodes come in a horizontal format, it makes sense to also convey this in our scanning interface. This is mainly a cosmetic change, but do keep in mind that only barcodes located inside the viewfinder will be scanned.
config.viewFinder.aspectRatio.height = 1;
config.viewFinder.aspectRatio.width = 3;
The result looks like this:
<!-- Existing code -->
document
.getElementById("start-scanning")
.addEventListener("click", async () => {
const config =
new ScanbotSDK.UI.Config.BarcodeScannerScreenConfiguration();
config.scannerConfiguration.barcodeFormats = ["PDF_417"];
config.viewFinder.aspectRatio.height = 1;
config.viewFinder.aspectRatio.width = 3;
const scanResult = await ScanbotSDK.UI.createBarcodeScanner(config);
if (scanResult?.items?.length > 0) {
document.getElementById("result").innerText =
`Barcode type: ${scanResult.items[0].barcode.format} \n` +
`Barcode content: "${scanResult.items[0].barcode.text}" \n`;
} else {
document.getElementById("result").innerText = "Scanning aborted by the user";
}
});
Step 3: Implement the AAMVA parser
The information on US and Canadian driver’s licenses follows a standard created by The American Association of Motor Vehicle Administrators (AAMVA) and the Department of Motor Vehicles (DMV). This information is also encoded in a PDF417 barcode on the back.
There are 22 mandatory data elements that these barcodes contain. The three letters in front act as element IDs.
- DCA: Vehicle class(es) the cardholder is allowed to drive
- DCB: Restrictions on driving privileges (e.g., only automatic transmission)
- DCD: Additional privileges granted to the cardholder (e.g., transportation of hazardous material)
- DBA: Expiration date of the document
- DCS: Last name / family name
- DAC: First name
- DAD: Middle name(s)
- DBD: Issue date of the document
- DBB: Date of birth
- DBC: Gender
- DAY: Eye color
- DAU: Height
- DAG: Address: Street name
- DAI: Address: City name
- DAJ: Address: State name
- DAK: Address: Postal code
- DAQ: ID number of the cardholder
- DCF: ID number of the document
- DCG: Country in which the document was issued
- DDE: Indicator that the last name is truncated
- DDF: Indicator that the first name is truncated
- DDG: Indicator that the middle name(s) are truncated
Without parsing this data structure, the output of PDF417 barcodes on driver’s licenses is hard to read. Fortunately, the Scanbot Web SDK comes with a built-in AAMVA barcode parser. Let’s implement it in our code. We’ll need to change what happens in the if (scanResult?.items?.length > 0)
statement.
<!-- Existing code -->
if (scanResult?.items?.length > 0) {
const extractedDocument =
scanResult?.items[0].barcode.extractedDocument;
function extractDocumentFields(document) {
const fields = [];
const processFields = (fieldsArray) => {
if (!fieldsArray || !Array.isArray(fieldsArray))
return;
fieldsArray.forEach((field) => {
if (field.type?.name && field.value?.text) {
fields.push(
field.type.name +
" : " +
field.value.text,
);
}
});
};
processFields(document.fields);
if (
document.children &&
Array.isArray(document.children)
) {
document.children.forEach((child) =>
processFields(child.fields),
);
}
return fields;
}
const documentResult =
extractDocumentFields(extractedDocument);
document.getElementById("result").innerText =
documentResult.join("\n");
} else {
document.getElementById("result").innerText =
"Scanning aborted by the user";
}
Let’s break this down:
- We define a function
extractDocumentFields
that takes a document object as an argument and extracts its fields into an array. Inside this function, we define another functionprocessFields
to process an array of fields. This function checks if the provided array is valid and iterates over each field, pushing the field’s name and text value into thefields
array if they exist. - The
extractDocumentFields
function first processes the fields of the main document. It then checks if the document has any children and, if so, processes the fields of each child document as well. The result is an array of field names and values. - After defining the function, the script calls
extractDocumentFields
with the extracted document and stores the result indocumentResult
. It then updates the inner text of the “result” HTML element to display the extracted fields, joined by newline characters.
The final code will look like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
<title>AAMVA Barcode Parser</title>
</head>
<body style="margin: 0">
<button id="start-scanning">Start scanning</button>
<p id="result"></p>
<script type="module">
import "https://cdn.jsdelivr.net/npm/scanbot-web-sdk@7.0.0/bundle/ScanbotSDK.ui2.min.js";
const sdk = await ScanbotSDK.initialize({
enginePath:
"https://cdn.jsdelivr.net/npm/scanbot-web-sdk@7.0.0/bundle/bin/barcode-scanner/",
});
document
.getElementById("start-scanning")
.addEventListener("click", async () => {
const config =
new ScanbotSDK.UI.Config.BarcodeScannerScreenConfiguration();
config.scannerConfiguration.barcodeFormats = ["PDF_417"];
config.viewFinder.aspectRatio.height = 1;
config.viewFinder.aspectRatio.width = 3;
const scanResult = await ScanbotSDK.UI.createBarcodeScanner(config);
if (scanResult?.items?.length > 0) {
const extractedDocument =
scanResult?.items[0].barcode.extractedDocument;
function extractDocumentFields(document) {
const fields = [];
const processFields = (fieldsArray) => {
if (!fieldsArray || !Array.isArray(fieldsArray))
return;
fieldsArray.forEach((field) => {
if (field.type?.name && field.value?.text) {
fields.push(
field.type.name +
" : " +
field.value.text,
);
}
});
};
processFields(document.fields);
if (
document.children &&
Array.isArray(document.children)
) {
document.children.forEach((child) =>
processFields(child.fields),
);
}
return fields;
}
const documentResult =
extractDocumentFields(extractedDocument);
document.getElementById("result").innerText =
documentResult.join("\n");
} else {
document.getElementById("result").innerText =
"Scanning aborted by the user";
}
});
</script>
</body>
</html>
🎉 And with this, we’ve completed our AAMVA barcode parser!
Now run the app once more and scan a driver’s license barcode …

… to test your scanner!

Conclusion
This is just one of the many scanner configurations the Scanbot SDK has to offer – take a look at the RTU UI documentation and API reference to learn more.
Should you have questions about this tutorial or run into any issues, we’re happy to help! Just shoot us an email via tutorial-support@scanbot.io.
Happy scanning! 🤳