Skip to content

How to build an AAMVA barcode parser in JavaScript

Kevin March 19, 2025 8 mins read
app store

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.

Scanning an AAMVA barcode with the data parser enabled

To achieve this, we’ll use plain JavaScript and the Scanbot Web Barcode Scanner SDK. We’re going to follow these steps:

  1. Setting up the barcode scanner
  2. Optimizing the scanner for PDF417 barcodes
  3. 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:

  1. Create a button that calls up the scanning interface when clicked.
  2. Include a <p> element on the page for displaying the scanning result.
  3. Import the Scanbot Web SDK using a CDN.
  4. 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.

Scanning an AAMVA barcode without a data parser

💡 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.

  1. DCA: Vehicle class(es) the cardholder is allowed to drive
  2. DCB: Restrictions on driving privileges (e.g., only automatic transmission)
  3. DCD: Additional privileges granted to the cardholder (e.g., transportation of hazardous material)
  4. DBA: Expiration date of the document
  5. DCS: Last name / family name
  6. DAC: First name
  7. DAD: Middle name(s)
  8. DBD: Issue date of the document
  9. DBB: Date of birth
  10. DBC: Gender
  11. DAY: Eye color
  12. DAU: Height
  13. DAG: Address: Street name
  14. DAI: Address: City name
  15. DAJ: Address: State name
  16. DAK: Address: Postal code
  17. DAQ: ID number of the cardholder
  18. DCF: ID number of the document
  19. DCG: Country in which the document was issued
  20. DDE: Indicator that the last name is truncated
  21. DDF: Indicator that the first name is truncated
  22. 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:

  1. 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 function processFields 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 the fields array if they exist.
  2. 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.
  3. After defining the function, the script calls extractDocumentFields with the extracted document and stores the result in documentResult. 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 …

Example AAMVA barcode on an American driver's license

… to test your scanner!

Scanning an AAMVA barcode with the data parser enabled

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! 🤳