How to Build an Angular MRZ Scanner and Passport Reader App

The Dynamsoft Capture Vision SDK (DCV) is a modular and unified SDK that integrates barcode scanning, document detection, MRZ recognition, and more under a single pipeline-based API. One of its key modules is the MRZ scanner, which provides machine-readable zone recognition for passports, visas, and other travel documents.

This article focuses on the MRZ (Machine Readable Zone) recognition capabilities of the Dynamsoft Capture Vision SDK. You will learn how to integrate the SDK into an Angular web application to perform MRZ scanning from both image files and live camera streams.

What you’ll build: A fully functional Angular web application that reads passport MRZ zones from uploaded images and live camera feeds, extracts structured traveler data (name, nationality, document number, dates), and displays parsed results in real time — powered by the Dynamsoft Capture Vision SDK.

Key Takeaways

  • The Dynamsoft Capture Vision SDK provides a unified, pipeline-based API for MRZ recognition in Angular apps, supporting TD1, TD2, and TD3 document types (passports, visas, and national ID cards).
  • MRZ reading from static image files uses CaptureVisionRouter.capture() with a ReadMRZ template, while real-time camera scanning uses CameraEnhancer bound to the same router.
  • The CodeParser module converts raw two-line or three-line MRZ strings into structured fields — surname, given name, nationality, passport number, birth date, and expiry date — without manual regex parsing.
  • This approach works on all modern browsers and supports both desktop webcams and mobile device cameras through the CameraEnhancer API.

Common Developer Questions

  • How do I read passport MRZ data in an Angular web application?
  • What JavaScript SDK can scan and parse MRZ from a live camera stream in the browser?
  • How do I extract structured passport fields (name, nationality, expiry) from an MRZ image using TypeScript?

See the Angular MRZ Scanner in Action

Prerequisites

Step 1: Install the Dynamsoft Capture Vision SDK

  1. Install the SDK: Use npm to download and install the necessary packages dynamsoft-capture-vision-bundle and dynamsoft-capture-vision-data.

     npm i dynamsoft-capture-vision-bundle dynamsoft-capture-vision-data
    

    Explanation

    • The dynamsoft-capture-vision-bundle package contains the core library of Dynamsoft Capture Vision.
    • The dynamsoft-capture-vision-data package provides model files for Dynamsoft Capture Vision.
  2. Configure Asset Path: Update angular.json to include the paths to the new packages.

     "assets": [
       "src/favicon.ico",
       "src/assets",
       ...
       {
         "glob": "**/*",
         "input": "./node_modules/dynamsoft-capture-vision-bundle/dist",
         "output": "assets/dynamsoft-capture-vision-bundle"
       },
       {
         "glob": "**/*",
         "input": "./node_modules/dynamsoft-capture-vision-data",
         "output": "assets/dynamsoft-capture-vision-data"
       },
     ],
    
  3. Update Resource Paths in TypeScript Code: Define the full resource paths in the CoreModule.engineResourcePaths object within the product-list.component.ts file.

     export function getFullUrl(endpoint: string): string {
         let baseUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
         return `${baseUrl}${endpoint}`;
     }
    
     CoreModule.engineResourcePaths = {
       dcvBundle: getFullUrl('assets/dynamsoft-capture-vision-bundle/'),
       dcvData: getFullUrl('assets/dynamsoft-capture-vision-data/'),
     };
    

Step 2: Create the MRZ Reader and Scanner Components

  1. Generate Components: Use the Angular CLI to create two new components: MRZ Reader and MRZ Scanner.

     ng generate component mrz-reader
     ng generate component mrz-scanner
    
  2. Configure Routing: Update the app-routing.module.ts file to include routes for the new components.

     import { NgModule } from '@angular/core';
     import { RouterModule, Routes } from '@angular/router';
     import { ProductListComponent } from './product-list/product-list.component';
     import { BarcodeReaderComponent } from './barcode-reader/barcode-reader.component';
     import { BarcodeScannerComponent } from './barcode-scanner/barcode-scanner.component';
     import { FileDetectionComponent } from './file-detection/file-detection.component';
     import { CameraDetectionComponent } from './camera-detection/camera-detection.component';
     import { MrzReaderComponent } from './mrz-reader/mrz-reader.component';
     import { MrzScannerComponent } from './mrz-scanner/mrz-scanner.component';
        
     const routes: Routes = [
       { path: '', component: ProductListComponent },
       { path: 'barcode-reader', component: BarcodeReaderComponent },
       { path: 'barcode-scanner', component: BarcodeScannerComponent },
       { path: 'file-detection', component: FileDetectionComponent },
       { path: 'camera-detection', component: CameraDetectionComponent },
       { path: 'mrz-reader', component: MrzReaderComponent },
       { path: 'mrz-scanner', component: MrzScannerComponent }
     ];
    
  3. Update Product List Template: Add navigation links to the new components in the product-list.component.html file.

     <h3>
       <div>
         <ng-template [ngIf]="product.id === 'reader'">
           <a [title]="product.name + ' details'" [routerLink]="['/barcode-reader']">>
                
           </a>
         </ng-template>
       </div>
    
       <div>
         <ng-template [ngIf]="product.id === 'scanner'">
           <a [title]="product.name + ' details'" [routerLink]="['/barcode-scanner']">>
                
           </a>
         </ng-template>
       </div>
    
       <div>
         <ng-template [ngIf]="product.id === 'file-detection'">
           <a [title]="product.name + ' details'" [routerLink]="['/file-detection']">>
                
           </a>
         </ng-template>
       </div>
    
       <div>
         <ng-template [ngIf]="product.id === 'camera-detection'">
           <a [title]="product.name + ' details'" [routerLink]="['/camera-detection']">>
                
           </a>
         </ng-template>
       </div>
    
       <div>
         <ng-template [ngIf]="product.id === 'mrz-reader'">
           <a [title]="product.name + ' details'" [routerLink]="['/mrz-reader']">>
                
           </a>
         </ng-template>
       </div>
    
       <div>
         <ng-template [ngIf]="product.id === 'mrz-scanner'">
           <a [title]="product.name + ' details'" [routerLink]="['/mrz-scanner']">>
                
           </a>
         </ng-template>
       </div>
    
     </h3>
    

Step 3: Set Up the MRZ Recognition Template

The Dynamsoft Label Recognizer allows you to customize templates for various scenarios. For detecting MRZ characters in passports and other travel documents, we will create a template named ReadMRZ and save it to assets/template.json.

Step 4: Read and Parse MRZ Data from Image Files

Workflow Overview

  1. Upload an image file.
  2. Recognize the MRZ characters with the Dynamsoft Capture Vision SDK.
  3. Extract the MRZ data from the recognized text using Dynamsoft Code Parser Module.
  4. Display the results in a text area.

Build the File Upload UI

Add the following HTML code to the mrz-reader.component.html file:

<div id="loading-indicator" class="loading-indicator" *ngIf="!isLoaded">
    <div class="spinner"></div>
</div>

<div>
    <input type="file" title="file" id="file" accept="image/*" (change)="onChange($event)" />
</div>

<div class="container" id="file_container">


    <div class="row">
        <div id="imageview" class="imageview">
            <img id="image" alt="" />
            <canvas id="overlay" class="overlay"></canvas>

        </div>
    </div>



</div>

<div>
    <textarea id="detection_result"></textarea>
</div>

Explanation

  • The file input element allows users to upload an image file.
  • The image element displays the uploaded image.
  • The overlay canvas draws the text area boundaries.
  • The text area displays the MRZ data extracted from the image.

Implement the MRZ Reader Component

  1. Initialize the SDK: Create an instance of CaptureVisionRouter and load the MRZ template.

     ngOnInit(): void {
       this.overlayManager.initOverlay(document.getElementById('overlay') as HTMLCanvasElement);
    
       (async () => {
         try {
           await CodeParserModule.loadSpec("MRTD_TD1_ID");
           await CodeParserModule.loadSpec("MRTD_TD2_FRENCH_ID");
           await CodeParserModule.loadSpec("MRTD_TD2_ID");
           await CodeParserModule.loadSpec("MRTD_TD2_VISA");
           await CodeParserModule.loadSpec("MRTD_TD3_PASSPORT");
           await CodeParserModule.loadSpec("MRTD_TD3_VISA");
      
           await CaptureVisionRouter.appendModelBuffer("MRZCharRecognition");
           await CaptureVisionRouter.appendModelBuffer("MRZTextLineRecognition");
           this.cvr = await CaptureVisionRouter.createInstance();
           this.parser = await CodeParser.createInstance();
           let ret = await this.cvr.initSettings('assets/template.json');
           console.log(ret);
         }
         catch (ex) {
           alert(ex);
         }
         this.isLoaded = true;
       })();
     }
    
    
  2. Load an image file:

     onChange(event: Event) {
       let parser = this.parser;
       const element = event.currentTarget as HTMLInputElement;
       let textarea = document.getElementById('detection_result') as HTMLTextAreaElement;
       let fileList: FileList | null = element.files;
       if (fileList) {
         let file = fileList.item(0) as any;
         if (file) {
           let fr = new FileReader();
           fr.onload = (event: any) => {
             let image = document.getElementById('image') as HTMLImageElement;
             if (image) {
               image.src = event.target.result;
               const img = new Image();
               textarea.value = '';
               img.onload = (event: any) => {
                 this.overlayManager.updateOverlay(img.width, img.height);
                 // TODO
               };
               img.src = event.target.result;
             }
           };
           fr.readAsDataURL(file);
         }
       }
     }
    
  3. Recognize MRZ: Call the capture method with the ReadMRZ template to recognize MRZ from the image file.

     if (this.cvr) {
       this.cvr.capture(file, "ReadMRZ").then(async (result: CapturedResult) => {
         let txts: any = [];
         try {
           let items = result.items;
           if (items.length > 0) {
    
             for (var i = 0; i < items.length; ++i) {
               if (items[i].type !== EnumCapturedResultItemType.CRIT_TEXT_LINE) {
                 continue; // check if captured result item is a barcode
               }
    
               // TODO
    
               break;
             }
    
           }
         } catch (e) {
           alert(e);
         }
    
         if (txts.length === 0) {
           textarea.value = 'No MRZ detected.';
         }
       });
     }
    
  4. Display the Results: Cast the CapturedResult to TextLineResultItem and get the MRZ characters using the CodeParser instance.

     let item = items[i] as TextLineResultItem;
     let localization = item.location;
     this.overlayManager.drawOverlay(
       localization,
       ''
     );
    
     txts.push(item.text);
    
     textarea.value = txts.join('') + '\n';
    
     let parseResults = await parser?.parse(item.text);
     textarea.value += JSON.stringify(handleMrzParseResult(parseResults!)) + '\n';
    
     export function handleMrzParseResult(result: ParsedResultItem): any {
         const parseResultInfo: any = {};
         let type = result.getFieldValue("documentCode");
         parseResultInfo['Document Type'] = JSON.parse(result.jsonString).CodeType;
         let nation = result.getFieldValue("issuingState");
         parseResultInfo['Issuing State'] = nation;
         let surName = result.getFieldValue("primaryIdentifier");
         parseResultInfo['Surname'] = surName;
         let givenName = result.getFieldValue("secondaryIdentifier");
         parseResultInfo['Given Name'] = givenName;
         let passportNumber = type === "P" ? result.getFieldValue("passportNumber") : result.getFieldValue("documentNumber");
         parseResultInfo['Passport Number'] = passportNumber;
         let nationality = result.getFieldValue("nationality");
         parseResultInfo['Nationality'] = nationality;
         let gender = result.getFieldValue("sex");
         parseResultInfo["Gender"] = gender;
         let birthYear = result.getFieldValue("birthYear");
         let birthMonth = result.getFieldValue("birthMonth");
         let birthDay = result.getFieldValue("birthDay");
         if (parseInt(birthYear) > (new Date().getFullYear() % 100)) {
             birthYear = "19" + birthYear;
         } else {
             birthYear = "20" + birthYear;
         }
         parseResultInfo['Date of Birth (YYYY-MM-DD)'] = birthYear + "-" + birthMonth + "-" + birthDay
         let expiryYear = result.getFieldValue("expiryYear");
         let expiryMonth = result.getFieldValue("expiryMonth");
         let expiryDay = result.getFieldValue("expiryDay");
         if (parseInt(expiryYear) >= 60) {
             expiryYear = "19" + expiryYear;
         } else {
             expiryYear = "20" + expiryYear;
         }
         parseResultInfo["Date of Expiry (YYYY-MM-DD)"] = expiryYear + "-" + expiryMonth + "-" + expiryDay;
         return parseResultInfo;
     }
    

    Angular MRZ Detection from image file

Step 5: Scan and Parse MRZ Data from a Live Camera Stream

Workflow Overview

  1. Register an event listener with CaptureVisionRouter to receive detected text results.
  2. Bind a CameraEnhancer instance to the CaptureVisionRouter instance.
  3. Start the camera stream and MRZ recognition.
  4. Parse and extract the MRZ data in the callback function.
  5. Display the results in a text area.

Build the Camera Scanner UI

Add the following HTML code to the mrz-scanner.component.html file:

<div id="loading-indicator" class="loading-indicator" *ngIf="!isLoaded">
    <div class="spinner"></div>
</div>

<div class="select">
    <label for="videoSource">Video source: </label>
    <select id="videoSource" (change)="openCamera()"></select>
</div>

<div class="container">
    <div id="videoview">
        <div class="dce-video-container" id="videoContainer"></div>
        <canvas id="overlay"></canvas>
    </div>

</div>


<div>
    <textarea id="result"></textarea>
</div>

Explanation

  • The select element allows users to choose a video source.
  • The video container displays the camera stream.
  • The overlay canvas draws the text area boundaries in real-time.
  • The text area displays the MRZ data extracted from the camera stream.

Implement the MRZ Scanner Component

  1. Initialize the SDK and Register Callback: Create an instance of CaptureVisionRouter , load the MRZ template, and register a callback function to receive detected document edges. Bind a camera view to the CaptureVisionRouter instance.

     ngOnInit(): void {
       this.videoSelect = document.querySelector('select#videoSource') as HTMLSelectElement;
       this.overlayManager.initOverlay(document.getElementById('overlay') as HTMLCanvasElement);
       (async () => {
         await CodeParserModule.loadSpec("MRTD_TD1_ID");
         await CodeParserModule.loadSpec("MRTD_TD2_FRENCH_ID");
         await CodeParserModule.loadSpec("MRTD_TD2_ID");
         await CodeParserModule.loadSpec("MRTD_TD2_VISA");
         await CodeParserModule.loadSpec("MRTD_TD3_PASSPORT");
         await CodeParserModule.loadSpec("MRTD_TD3_VISA");
    
         await CaptureVisionRouter.appendModelBuffer("MRZCharRecognition");
         await CaptureVisionRouter.appendModelBuffer("MRZTextLineRecognition");
         this.cvr = await CaptureVisionRouter.createInstance();
         this.parser = await CodeParser.createInstance();
         let ret = await this.cvr.initSettings('assets/template.json');
         console.log(ret);
         await this.initBarcodeScanner();
       })();
     }
    
     async initBarcodeScanner(): Promise<void> {
       const cameraView: CameraView = await CameraView.createInstance();
    
       this.cameraEnhancer = await CameraEnhancer.createInstance(cameraView);
    
       let uiElement = document.getElementById('videoContainer');
       if (uiElement) {
         uiElement.append(cameraView.getUIElement());
    
         cameraView.getUIElement().shadowRoot?.querySelector('.dce-sel-camera')?.setAttribute('style', 'display: none');
         cameraView.getUIElement().shadowRoot?.querySelector('.dce-sel-resolution')?.setAttribute('style', 'display: none');
    
         let cameras = await this.cameraEnhancer.getAllCameras();
         this.listCameras(cameras);
    
         if (this.isDestroyed) {
           throw Error(componentDestroyedErrorMsg);
         }
         this.cvr?.setInput(this.cameraEnhancer);
    
         this.cvr?.addResultReceiver({
           onCapturedResultReceived: async (result: CapturedResult) => {
             // TODO
           },
         });
    
         this.cameraEnhancer.on('played', () => {
           this.updateResolution();
         });
         await this.openCamera();
         if (this.isDestroyed) {
           throw Error(componentDestroyedErrorMsg);
         }
         await this.cvr?.startCapturing('ReadMRZ');
         if (this.isDestroyed) {
           throw Error(componentDestroyedErrorMsg);
         }
    
         this.isLoaded = true;
       }
     }
    
  2. Process the MRZ Recognition Results: Implement the callback function to handle MRZ recognition results.

     onCapturedResultReceived: async (result: CapturedResult) => {
       this.overlayManager.clearOverlay();
       let txts: any = [];
       let resultElement = document.getElementById('result');
       try {
         let localization;
         let items = result.items
         if (items.length > 0) {
           for (var i = 0; i < items.length; ++i) {
    
             if (items[i].type !== EnumCapturedResultItemType.CRIT_TEXT_LINE) {
               continue;
             }
    
             let item = items[i] as TextLineResultItem;
    
             txts.push(item.text);
             localization = item.location;
             this.overlayManager.drawOverlay(
               localization,
               ''
             );
    
             let parseResults = await this.parser?.parse(item.text);
             if (resultElement) {
               resultElement.innerHTML = JSON.stringify(handleMrzParseResult(parseResults!)) + '\n';
             }
    
             break;
           }
    
         }
       } catch (e) {
         alert(e);
       }
     },
    

    Angular MRZ Detection from camera stream

Common Issues and Edge Cases

  • MRZ not detected on low-resolution images: The MRZ recognition model requires legible text. If the passport photo is blurry, low-DPI, or taken at a steep angle, capture() may return zero results. Use images with at least 300 DPI or ensure the camera is held steady and parallel to the document.
  • Camera permission denied in the browser: CameraEnhancer will fail silently if the user denies camera access or the page is not served over HTTPS. Always serve your Angular app via https:// in production, and handle the camera permission error gracefully in your component.
  • CodeParser returns empty fields for non-passport documents: The handleMrzParseResult function reads passportNumber only when documentCode is "P". For TD1 (national ID) and TD2 (visa) documents, make sure to read documentNumber instead. The template already supports all three TD types, but verify your parsing logic covers each case.

Source Code

https://github.com/yushulx/angular-barcode-mrz-document-scanner