@zxing/library, html5-qrcode, jsqr, qr-scanner, and qrcode-reader are JavaScript libraries that enable QR code detection and decoding directly in the browser using device cameras or image inputs. These packages vary significantly in scope, API design, performance characteristics, and maintenance status. While some provide full-featured camera integration with real-time scanning, others focus solely on decoding raw pixel data from images or video frames, requiring developers to handle media setup manually.
When you need to scan QR codes in a web app, you have several libraries to choose from β but they differ dramatically in what they do, how much work they save you, and whether theyβre still maintained. Letβs cut through the noise and compare the real engineering trade-offs.
qrcode-readerFirst, the easy decision: qrcode-reader is deprecated. Its npm page states itβs no longer maintained, and the GitHub repo hasnβt seen updates in years. Donβt use it in new projects. Weβll include it in comparisons only for completeness, but treat it as obsolete.
The biggest split among these libraries is whether they handle the entire scanning flow (camera access, UI, real-time decoding) or just the decoding step (turning pixel data into text).
html5-qrcode and qr-scannerThese packages manage everything:
html5-qrcode example:
import { Html5Qrcode } from "html5-qrcode";
const html5QrCode = new Html5Qrcode("reader");
html5QrCode.start(
{ facingMode: "environment" },
{ fps: 10, qrbox: { width: 250, height: 250 } },
(decodedText) => console.log("Found:", decodedText),
(errorMessage) => console.log("Error:", errorMessage)
);
qr-scanner example:
import QrScanner from "qr-scanner";
import QrFrame from "qr-scanner/qr-scanner.min.js";
const video = document.getElementById("video");
const scanner = new QrScanner(video, result => console.log(result));
scanner.start();
Note:
qr-scannerrequires you to include its worker script separately for optimal performance.
@zxing/library and jsqrThese expect you to provide raw image data (e.g., from a <canvas> or <video> frame). You handle camera setup, frame capture, and retry logic.
jsqr example:
import jsQR from "jsqr";
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
// Assume video is already streaming
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) console.log("Found:", code.data);
@zxing/library example:
import { BrowserMultiFormatReader } from "@zxing/library";
const codeReader = new BrowserMultiFormatReader();
const video = document.querySelector("video");
// Start decoding from video stream
const controls = await codeReader.decodeFromVideoDevice(
null,
video,
(result, error, controls) => {
if (result) console.log("Found:", result.getText());
}
);
@zxing/librarydoes offer high-level camera helpers (decodeFromVideoDevice), but theyβre less polished thanhtml5-qrcodeβs API.
jsqr runs entirely in the main thread. For smooth UI, you must throttle frame analysis (e.g., using requestAnimationFrame with skips).qr-scanner uses a Web Worker by default to avoid blocking the UI β a big plus for responsive apps.@zxing/library can be heavy because it supports many barcode formats; QR-only decoding is possible but not the default.html5-qrcode runs decoding on the main thread but lets you limit FPS to reduce load.Need to tweak the scanning region, add overlays, or handle multiple concurrent scans?
jsqr gives maximum control β you decide exactly which pixels to analyze and when.qr-scanner allows customizing the scan region and includes a built-in highlight box.html5-qrcode lets you configure the scan area (qrbox) but offers less visual customization.@zxing/library requires manual DOM manipulation for overlays but supports advanced decoding hints.All camera-enabled libraries must handle getUserMedia() permissions, but their approaches differ:
html5-qrcode includes built-in error messages for common issues (e.g., camera not found).qr-scanner throws clear errors but leaves UI feedback to you.@zxing/library propagates raw DOMExceptions β youβll need to map them to user-friendly messages.What if you want to scan from an uploaded image, not a camera?
html5-qrcode:
Html5Qrcode.scanFile(file).then(text => console.log(text));
qr-scanner:
QrScanner.scanImage(fileOrUrl).then(result => console.log(result));
jsqr and @zxing/library require you to draw the image to a canvas first, then extract pixel data β more code, but more flexible.
html5-qrcode uses separate success/error callbacks.qr-scanner returns promises that reject on errors.jsqr returns null on failure β no exceptions thrown.@zxing/library throws errors during decoding, which you must catch in your callback.| Library | Camera Built-in? | Worker Support? | File Scanning? | Multi-Format? | Actively Maintained? |
|---|---|---|---|---|---|
@zxing/library | Partial | β | Manual | β | β |
html5-qrcode | β | β | β | QR only | β |
jsqr | β | β | Manual | QR only | β |
qr-scanner | β | β | β | QR only | β |
qrcode-reader | β | β | Manual | QR only | β (Deprecated) |
html5-qrcode. Itβs the quickest path to a working demo.qr-scanner with its worker-based decoding.jsqr for pixel-level control.@zxing/library.qrcode-reader.Choose based on whether you value convenience (html5-qrcode, qr-scanner) or control (jsqr, @zxing/library). And always check the official docs β APIs evolve, and assumptions can break.
Choose @zxing/library if you need a mature, multi-format barcode/QR scanning solution that supports not just QR codes but also many 1D and 2D barcode formats. It provides both low-level decoding APIs and higher-level continuous scanning capabilities, though it requires more manual setup for camera handling compared to turnkey solutions. Best suited for applications requiring broad symbology support beyond QR codes.
Choose html5-qrcode if you want a simple, all-in-one solution for real-time QR scanning from a webcam with minimal setup. It handles camera enumeration, permission requests, UI rendering, and decoding out of the box, making it ideal for quick integrations where developer experience and ease of use are prioritized over fine-grained control.
Choose jsqr if you're building a custom scanning pipeline and only need a lightweight, dependency-free decoder for static images or pre-captured video frames. It gives you complete control over the media input and processing loop but requires you to implement camera access, frame extraction, and scanning logic yourself. Perfect for performance-critical or highly customized scanning scenarios.
Choose qr-scanner if you need a balance between ease of use and performance, with built-in camera support and optional worker-based decoding for better responsiveness. It offers a clean API for both file and camera scanning, includes a highlight overlay for detected codes, and allows disabling worker usage if needed. A solid middle-ground choice for most web apps.
Do not choose qrcode-reader for new projects β it is officially deprecated according to its npm page and GitHub repository. The package has not been updated in years and lacks modern features like camera integration or performance optimizations. Use one of the actively maintained alternatives instead.
The project is in maintenance mode, meaning, changes are driven by contributed patches. Only bug fixes and minor enhancements will be considered. The Barcode Scanner app can no longer be published, so it's unlikely any changes will be accepted for it. There is otherwise no active development or roadmap for this project. It is "DIY".
If it doesn't, we gonna make it.
ZXing ("zebra crossing") is an open-source, multi-format 1D/2D barcode image processing library implemented in Java, with ports to other languages.
See Projects and Milestones for what is currently done and what's planned next. π
| 1D product | 1D industrial | 2D |
|---|---|---|
| UPC-A | Code 39 | QR Code |
| UPC-E | Code 93 | Data Matrix |
| EAN-8 | Code 128 | Aztec |
| EAN-13 | Codabar | PDF 417 |
| ITF | ||
| RSS-14 | ||
| RSS-Expanded (not production ready!) |
NOTE: While we do not have the time to actively maintain zxing-js anymore, we are open to new maintainers taking the lead.
See Live Preview in browser.
Note: All the examples are using ES6, be sure is supported in your browser or modify as needed, Chrome recommended.
npm i @zxing/library --save
or
yarn add @zxing/library
On iOS-Devices with iOS < 14.3 camera access works only in native Safari and not in other Browsers (Chrome,...) or Apps that use an UIWebView or WKWebView. This is not a restriction of this library but of the limited WebRTC support by Apple. The behavior might change in iOS 11.3 (Apr 2018?, not tested) as stated here
iOS 14.3 (released in december 2020) now supports WebRTC in 3rd party browsers as well π
The browser layer is using the MediaDevices web API which is not supported by older browsers.
You can use external polyfills like WebRTC adapter to increase browser compatibility.
Also, note that the library is using the TypedArray (Int32Array, Uint8ClampedArray, etc.) which are not available in older browsers (e.g. Android 4 default browser).
You can use core-js to add support to these browsers.
In the PDF 417 decoder recent addition, the library now makes use of the new BigInt type, which is not supported by all browsers as well. There's no way to polyfill that and ponyfill libraries are way to big, but even if PDF 417 decoding relies on BigInt the rest of the library shall work ok in browsers that doesn't support it.
There's no polyfills for BigInt in the way it's coded in here.
// use with commonJS
const { MultiFormatReader, BarcodeFormat } = require('@zxing/library');
// or with ES6 modules
import { MultiFormatReader, BarcodeFormat } from '@zxing/library';
const hints = new Map();
const formats = [BarcodeFormat.QR_CODE, BarcodeFormat.DATA_MATRIX/*, ...*/];
hints.set(DecodeHintType.POSSIBLE_FORMATS, formats);
const reader = new MultiFormatReader();
const luminanceSource = new RGBLuminanceSource(imgByteArray, imgWidth, imgHeight);
const binaryBitmap = new BinaryBitmap(new HybridBinarizer(luminanceSource));
reader.decode(binaryBitmap, hints);
See Contributing Guide for information regarding porting approach and reasoning behind some of the approaches taken.
Special thanks to all the contributors who have contributed for this project. We heartly thankful to you all.
And a special thanks to @aleris who created the project itself and made available the initial QR code port.