How to Build a Web-Based MRZ and VIN Scanner with JavaScript and HTML5
In the digital era, extracting information from documents and vehicle IDs has become increasingly important for web-based applications. Machine Readable Zones (MRZ) and Vehicle Identification Numbers (VIN) can now be scanned directly in the browser using modern web technologies. In this tutorial, you’ll learn how to build a web-based MRZ and VIN scanner using JavaScript, HTML5 and Dynamsoft Capture Vision SDK.
What you’ll build: A browser-based MRZ and VIN scanner that uses the Dynamsoft Capture Vision SDK to read passport/ID machine-readable zones and vehicle identification numbers from both image files and a live camera stream — all in pure JavaScript with no native dependencies.
Key Takeaways
- Dynamsoft Capture Vision SDK enables real-time MRZ and VIN scanning directly in the browser with JavaScript and HTML5 — no native app or plugin required.
CaptureVisionRouterwith theReadMRZandReadVINTexttemplates drives all OCR inference;CameraEnhancerwrapsgetUserMedia()and handles live stream display.- Both file-based and live-camera scanning are supported with the same SDK instance, switchable at runtime via a radio toggle.
- This approach supports TD1, TD2, and TD3 passport MRZ formats as well as standard 17-character VINs across all major modern browsers.
Common Developer Questions
- How do I scan MRZ from a passport or ID card photo using JavaScript in the browser?
- How do I read VIN text or a VIN barcode from a camera feed in an HTML5 web app?
- What is the difference between
capture()andstartCapturing()in the Dynamsoft Capture Vision SDK?
This article is Part 2 in a 3-Part Series.
See It in Action: Web MRZ/VIN Scanner Demo
Prerequisites
- Get a 30-day free trial license for Dynamsoft Capture Vision.
Online Demo
Step 1: Install the Dynamsoft Capture Vision Bundle
The dynamsoft-capture-vision-bundle is the JavaScript version of Dynamsoft Capture Vision, available via npm or CDN. To use it, include the library in your index.html:
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-bundle@3.0.3001/dist/dcv.bundle.min.js"></script>
Step 2: Set Up the HTML Structure
The target HTML layout for the MRZ/VIN scanner consists of three main sections:
-
A
divelement for license key setup, input source selection (File or Camera), and scanning mode toggle (MRZ or VIN).<div class="container"> <div class="row"> <div> <label> Get a License key from <a href="https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform" target="_blank">here</a> </label> <input type="text" id="license_key" value="LICENSE-KEY" placeholder="LICENSE-KEY"> <button onclick="activate()">Activate SDK</button> </div> </div> <div class="row"> <div> <select onchange="selectChanged()" id="dropdown"> <option value="file">File</option> <option value="camera">Camera</option> </select> <form id="modeSelector"> <label> <input type="radio" name="scanMode" value="mrz" checked> MRZ </label> <label> <input type="radio" name="scanMode" value="vin"> VIN </label> </form> </div> </div> </div> -
A
divfor displaying the uploaded image and its scanning result.<div class="container" id="file_container"> <div> <input type="file" id="pick_file" accept="image/*" /> </div> <div class="row"> <div class="imageview"> <img id="image_file" src="default.png" /> <canvas id="overlay_canvas" class="overlay"></canvas> </div> </div> <div class="row"> <div> <textarea id="detection_result"></textarea> </div> </div> </div> -
A
devfor showing the live camera stream along with real-time scanning results.<div class="container" id="camera_container"> <div> <select onchange="cameraChanged()" id="camera_source"> </select> <button onclick="scan()" id="scan_button">Start</button> <div id="videoview"> <div id="camera_view"></div> </div> <div class="row"> <div> <textarea id="scan_result"></textarea> </div> </div> </div> </div>
Step 3: Initialize the MRZ and VIN Recognition Engines
The recognition engines for MRZ and VIN are initialized in the activate() function, which is triggered when the user clicks the Activate SDK button. This function sets up the license key, loads the required models and code parsers, and registers result receivers for both MRZ and VIN.
async function activate() {
toggleLoading(true);
let divElement = document.getElementById("license_key");
let licenseKey = divElement.value == "" ? divElement.placeholder : divElement.value;
try {
await Dynamsoft.License.LicenseManager.initLicense(
licenseKey,
true
);
Dynamsoft.Core.CoreModule.loadWasm(["DLR"]);
parser = await Dynamsoft.DCP.CodeParser.createInstance();
// Load VIN and MRZ models
await Dynamsoft.DCP.CodeParserModule.loadSpec("VIN");
await Dynamsoft.CVR.CaptureVisionRouter.appendModelBuffer("VINCharRecognition");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD1_ID");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD2_FRENCH_ID");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD2_ID");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD2_VISA");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD3_PASSPORT");
await Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD3_VISA");
await Dynamsoft.CVR.CaptureVisionRouter.appendModelBuffer("MRZCharRecognition");
await Dynamsoft.CVR.CaptureVisionRouter.appendModelBuffer("MRZTextLineRecognition");
mrzRouter = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
await mrzRouter.initSettings("./full.json");
mrzRouter.addResultReceiver({
onCapturedResultReceived: (result) => {
// TODO: Handle MRZ result
},
});
vinRouter = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
await vinRouter.initSettings("./full.json");
vinRouter.addResultReceiver({
onCapturedResultReceived: (result) => {
// TODO: Handle MRZ result
},
});
isSDKReady = true;
}
catch (ex) {
console.error(ex);
}
toggleLoading(false);
}
Step 4: Access the Camera with CameraEnhancer
Dynamsoft Capture Vision SDK provides the CameraEnhancer and CameraView classes for managing camera access and display. CameraEnhancer wraps the getUserMedia() method, while CameraView class adds a live video view to the DOM.
async function openCamera(cameraEnhancer, cameraInfo) {
if (!Dynamsoft) return;
try {
await cameraEnhancer.selectCamera(cameraInfo);
cameraEnhancer.on("played", function () {
resolution = cameraEnhancer.getResolution();
});
await cameraEnhancer.open();
}
catch (ex) {
console.error(ex);
}
}
async function closeCamera(cameraEnhancer) {
if (!Dynamsoft) return;
try {
await cameraEnhancer.close();
}
catch (ex) {
console.error(ex);
}
}
async function setResolution(cameraEnhancer, width, height) {
if (!Dynamsoft) return;
try {
await cameraEnhancer.setResolution(width, height);
}
catch (ex) {
console.error(ex);
}
}
async function initCamera() {
if (!Dynamsoft) return;
try {
cameraView = await Dynamsoft.DCE.CameraView.createInstance();
cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance(cameraView);
let scanRegion = {
x: 10,
y: 30,
width: 80,
height: 40,
isMeasuredInPercentage: true
};
cameraEnhancer.setScanRegion(scanRegion);
cameras = await cameraEnhancer.getAllCameras();
if (cameras != null && cameras.length > 0) {
for (let i = 0; i < cameras.length; i++) {
let option = document.createElement("option");
option.text = cameras[i].label;
cameraSource.add(option);
}
try {
let uiElement = document.getElementById("camera_view");
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');
}
catch (ex) {
console.error(ex);
}
}
else {
alert("No camera found.");
}
}
catch (ex) {
console.error(ex);
}
}
async function cameraChanged() {
if (cameras != null && cameras.length > 0) {
let index = cameraSource.selectedIndex;
await openCamera(cameraEnhancer, cameras[index]);
}
}
Step 5: Implement the MRZ/VIN Scanner Logic
To recognize MRZ and VIN from images or camera streams, use the capture() and startCapturing() methods respectively. The capture() method returns the recognition results directly, while the startCapturing() method starts a continuous capturing process and returns the results through the onCapturedResultReceived callback.
- Recognizing MRZ/VIN from Image Files
function loadImage2Canvas(base64Image) { imageFile.src = base64Image; img.src = base64Image; img.onload = function () { let width = img.width; let height = img.height; overlayCanvas.width = width; overlayCanvas.height = height; if (!isSDKReady) { alert("Please activate the SDK first."); return; } toggleLoading(true); let selectedMode = document.querySelector('input[name="scanMode"]:checked').value; let context = overlayCanvas.getContext('2d'); context.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); try { if (selectedMode == "mrz") { mrzRouter.capture(img.src, "ReadMRZ").then((result) => { showFileResult(selectedMode, context, result); }); } else if (selectedMode == "vin") { vinRouter.capture(img.src, "ReadVINText").then((result) => { showFileResult(selectedMode, context, result); }); } } catch (ex) { console.error(ex); } toggleLoading(false); }; } - Recognizing MRZ/VIN from Camera Stream
async function scan() { if (!isSDKReady) { alert("Please activate the SDK first."); return; } let selectedMode = document.querySelector('input[name="scanMode"]:checked').value; if (!isDetecting) { scanButton.innerHTML = "Stop"; isDetecting = true; if (selectedMode == "mrz") { mrzRouter.setInput(cameraEnhancer); mrzRouter.startCapturing("ReadMRZ"); } else if (selectedMode == "vin") { vinRouter.setInput(cameraEnhancer); vinRouter.startCapturing("ReadVINText"); } } else { scanButton.innerHTML = "Scan"; isDetecting = false; if (selectedMode == "mrz") { mrzRouter.stopCapturing(); } else if (selectedMode == "vin") { vinRouter.stopCapturing(); } cameraView.clearAllInnerDrawingItems(); } }
Step 6: Draw Overlays and Display Results
You can draw overlays on the image or video stream to highlight recognized areas and display the parsed results in a text area.
- On Image Files
async function showFileResult(selectedMode, context, result) { let parseResults = ''; let detection_result = document.getElementById('detection_result'); detection_result.innerHTML = ""; let txts = []; let items = result.items; if (items.length > 0) { for (var i = 0; i < items.length; ++i) { if (items[i].type !== Dynamsoft.Core.EnumCapturedResultItemType.CRIT_TEXT_LINE) { continue; } let item = items[i]; parseResults = await parser.parse(item.text); txts.push(item.text); localization = item.location; context.strokeStyle = '#ff0000'; context.lineWidth = 2; let points = localization.points; context.beginPath(); context.moveTo(points[0].x, points[0].y); context.lineTo(points[1].x, points[1].y); context.lineTo(points[2].x, points[2].y); context.lineTo(points[3].x, points[3].y); context.closePath(); context.stroke(); } } if (txts.length > 0) { detection_result.innerHTML += txts.join('\n') + '\n\n'; if (selectedMode == "mrz") { detection_result.innerHTML += JSON.stringify(extractMrzInfo(parseResults)); } else if (selectedMode == "vin") { detection_result.innerHTML += JSON.stringify(extractVinInfo(parseResults)); } } else { detection_result.innerHTML += "Recognition Failed\n"; } } -
On Camera Stream
async function showCameraResult(result) { let selectedMode = document.querySelector('input[name="scanMode"]:checked').value; let items = result.items; let scan_result = document.getElementById('scan_result'); if (items != null && items.length > 0) { let item = items[0]; let parseResults = await parser.parse(item.text); if (selectedMode == "mrz") { scan_result.innerHTML = JSON.stringify(extractMrzInfo(parseResults)); } else if (selectedMode == "vin") { scan_result.innerHTML = JSON.stringify(extractVinInfo(parseResults)); } } }
Step 7: Run the Web MRZ/VIN Scanner
- Open your terminal and navigate to the project directory.
- Start a local server using Python:
python -m http.server 8000 -
Open your web browser and navigate to
http://localhost:8000.
Common Issues & Edge Cases
initLicensefails with “License key invalid”: Ensure the license key entered in the input field is valid and not expired. The SDK validates the license asynchronously — always checkisSDKReadybefore callingcapture()orstartCapturing(), and wrap initialization in a try/catch to surface errors early.- Camera not found or
getUserMediadenied: Browsers require HTTPS (orlocalhost) for camera access. Serving over plain HTTP on any non-localhost domain will causegetUserMedia()to throw aNotAllowedError. Run locally withpython -m http.serveror deploy behind TLS. - MRZ recognition fails on blurry or skewed documents: The
setScanRegion()call restricts capture to 80×40% of the frame center. If the document occupies less than this region, or is tilted beyond ~15°, recognition rate drops. Move the document closer to the camera or widen the scan region dimensions. For VIN scanning, ensure strong contrast between the VIN characters and the vehicle surface.
Source Code
https://github.com/yushulx/javascript-barcode-qr-code-scanner/tree/main/examples/mrz-vin-scanner