SDK Documentation Help

Scanning PDF417 Barcodes on US Driving Licenses for Age Verification

A step-by-step guide on how to implement a web app that performs an age check by scanning the PDF417 barcode on a US driver’s license using the STRICH Barcode Scanning SDK. Full sample code is available on Github.

Introduction to PDF417

PDF417, standing for Portable Data File 417, is a two-dimensional barcode symbology known for its grid-like appearance. Designed as a stacked barcode, PDF417 appears as several 1D barcodes stacked atop each other. Below is an example PDF417 barcode that encodes the data Hi, I am a PDF417 barcode.

PDF417 barcode example

PDF417 uses Reed-Solomon error correction, ensuring that even if a section of the barcode is damaged, the data remains recoverable. Its robustness, combined with its data capacity, makes it a popular choice for applications that require substantial data storage in a compact form.

Another defining characteristic of PDF417 is that it’s readable with laser scanners that only scan in one direction (horizontally), by making multiple passes in vertical direction.

PDF417 Structure

What does “417” in PDF417 stand for? To understand that, we have to take a closer look at the symbology’s structure. As mentioned previously, PDF417 is a stacked symbology, meaning that it’s composed multiple 1D barcodes (let’s call them rows) on top of each other. Each row is composed of characters:

Start character: denotes the start of a row
Left row indicator: encodes the row number and other metadata
1-N data characters: encode the actual payload
Right row indicator: encodes row number and other metadata
Stop character: encodes end of row

PDF417 symbol structure

Each character is comprised of a sequence of 4 bars and 4 spaces, with a total width 17 modules. And that’s why it’s called PDF417.

PDF417 character structure

Maybe that was more detail than what you were asking for, but there you have it. Let’s move on to practical applications.

PDF417 in US Driver’s Licenses

One example of PDF417 applications are US driver’s licenses. US driver’s licenses are defined by the AAMVA, for detailed information please refer to the AAMVA DL/ID Card Design Standard (2020).

Here’s an example of a Pennsylvania driving license, with synthetic data:

Sample US driver's license

The barcode encodes a lot of information about the license holder, for instance: name, address, date of birth and more. That is sensitive information and should be handled with care.

Age Verification with an AAMVA PDF417 Barcode

In this example, we would like to perform an age check. We can do that by scanning the PDF417 barcode and comparing it to a reference age, say 21.

If you dig into the AAMVA document, you realize that the driver’s license data is encoded into a long string, separated into groups and fields by control characters. The date of birth is encoded in the DBB field, and it is a mandatory field — which is good, so we can rely on it being there.

This what the AAMVA spec has to say on the DBB field:

Excerpt from AAMVA spec, regarding DBB

The format of the DBB field is F8N, which is a shorthand for:
(F )ixed length: the field has a fixed length of 8 characters
(N )umeric: the field is comprised of numbers

The example for DBB given in the spec is DBB06061986<LF> where <LF> denotes the line feed character (ASCII code 10).

In addition to the date, we might also want to display the license holder’s first (DAC) and last name (DCS), both of which are mandatory variable-length fields with format identifier V40ANS (variable-length, 40 alphabetic, numeric and special characters).

For simplicity, we will treat the PDF417 data as a string and extract the DBB, DAC and DCS fields using regular expressions and not perform any additional validation. Obviously we are not going to write production code.

Building the App

To keep the app as simple and generic as possible, we will refrain from using any web application frameworks like Next.js, Angular or Vue and just stick to plain JavaScript-based DOM manipulation.

Layout & Styling

The app will consist of a single HTML file, containing a full-screen container element where the barcode scanner will live, and two dialog elements — one for the success case (age equal or above reference age), and one for the failure case (age below reference age). Dialog elements are built-in elements that allow for modal popups without any third-party libraries.

Screenshot of PDF417 sample age verification app

The HTML doesn’t contain any surprises.

<html lang="en"> <head> <title>PDF417 Age Check</title> <link rel="stylesheet" type="text/css" href="styles.css"/> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> </head> <body> <div class="container"> <!-- this is where the barcode scanner will live --> </div> <dialog id="success" class="success-popup"> <h1 class="popup-name"></h1> <h2 class="popup-age"></h2> <form method="dialog"> <button autofocus class="popup-button">OK</button> </form> </dialog> <dialog id="reject" class="reject-popup"> <h1 class="popup-name"></h1> <h2 class="popup-age"></h2> <form method="dialog"> <button autofocus class="popup-button">OK</button> </form> </dialog> <!-- our app code --> <script type="module" src="index.js"></script> </body> </html>

We’ll keep the styling to a minimum. The linked CSS makes the success dialog have a green background, the failure dialog have a red one, and makes the OK button “fat finger compatible” (full-width with added padding). We’re not going to win beauty prizes and that's ok.

Building the code

In a nutshell, the JavaScript code does the following:

SDK and BarcodeReader initialization — initialize the SDK for use and provide it with a valid license key. Initialize a BarcodeReader instance and configure it to look for PDF417 barcodes and use the container as its host element.

Barcode data processing — provide the detected hook that parses the PDF417 data, executes the age check, and displays the success or failure dialog.

AAMVA data extraction — regular expression-based parsing of date of birth, last name and first name fields from raw PDF417 data. For details, see aamva.js. In a real application, you would be doing a lot more validation and supporting of older AAMVA specification versions.

Here’s the code:

// import STRICH SDK as an ES6 module directly from a CDN import {StrichSDK, BarcodeReader} from ''; // AAMVA helper routines import {parseAAMVALicenseData} from "./aamva.js"; function processPDF417Barcode(codeDetection) { // attempt to parse barcode data as AAMVA driver's license const parsed = parseAAMVALicenseData(; if (!parsed) { console.error('PDF417 data could not be parsed according to AAMVA spec'); return; } // calculate age from system time const age = new Date().getFullYear() - parsed.dateOfBirth.getFullYear(); // depending on age, show success or reject popup const dialog = document.getElementById(age < 21 ? 'failure' : 'success'); dialog.getElementsByClassName('popup-name')[0].innerText = parsed.firstName + ' ' + parsed.lastName; dialog.getElementsByClassName('popup-age')[0].innerText = age + ' yrs old'; dialog.showModal(); } // Initialize BarcodeReader with appropriate settings for PDF417 function initializeBarcodeReader() { let configuration = { selector: '.container', engine: { symbologies: ['pdf417'] }, locator: { regionOfInterest: { // PDF417 is typically a wide rectangle, size the ROI appropriately left: 0.05, right: 0.05, top: 0.3, bottom: 0.3 } }, frameSource: { // high resolution recommended for PDF417 resolution: 'full-hd' }, }; return new BarcodeReader(configuration).initialize() .then(barcodeReader => { // store the BarcodeReader in a global, to be able to access it later (e.g. to destroy it) window['barcodeReader'] = barcodeReader; barcodeReader.detected = (detections) => { processPDF417Barcode(detections[0]); }; return barcodeReader.start(); }); } // initialize STRICH SDK with a valid license key const licenseKey = '<your license key here>'; StrichSDK.initialize(licenseKey) .then(() => { console.log('SDK initialized successfully'); return initializeBarcodeReader(); }) .catch(err => { // See: window.alert(err.localizedMessage); });

Typically STRICH SDK is installed via NPM: here we include the strich.js file as an ES6 module and obtain it from jsDelivr, a popular CDN.


To run the app, all you have to do is serve the files in the sample directory using an HTTP server. There is no build step involved — it’s just plain HTML and JavaScript after all.

If your system has Python installed, which is likely if you are a developer, you already have a barebones HTTP server at your disposal:

$ python -m http.server Serving HTTP on :: port 8000 (http://[::]:8000/) ... ::1 - - [07/Nov/2023 09:39:43] "GET / HTTP/1.1" 200 - ::1 - - [07/Nov/2023 09:39:44] "GET /index.js HTTP/1.1" 304 -

Unfortunately, web apps that access the camera need to be served from secure origins — a fancy word for “served over HTTPS or localhost”. We recommend using tools like ngrok or devtunnels to easily expose a local HTTP service through a publicly resolvable hostname and valid TLS certificate. More details on that topic are available in the Deployment Guide.

If you want to peak at the app in action but don’t want to set it up yourself, here’s a short clip:

Wrapping Up

In this article we showed how the PDF417 barcode on a US driver's license can be used to build a simple age verification web app. Full sample code is available on Github.

Last modified: 14 February 2024