Introduction
ZXing ("Zebra Crossing") started as Google's open-source barcode scanning library for Java and Android. The JavaScript port, @zxing/library, brings that same decoding engine to the browser. It handles QR codes, EAN-13, Code 128, DataMatrix, and over a dozen other barcode formats — all client-side, with no server round-trips.
Web-based scanning has real traction. Event platforms use it for ticket validation at venue entrances. Warehouse teams scan inventory barcodes from tablets running a progressive web app. Restaurants let diners scan table QR codes to pull up the menu without installing anything. The common thread: a camera, a browser, and a decoding library that runs fast enough for live video.
Installation and Setup
Install both the core decoding library and the browser helper package:
npm install @zxing/library @zxing/browser@zxing/library contains the format readers and decoding logic. @zxing/browser wraps the camera access and video element handling so you do not have to manage getUserMedia yourself.
For a quick CDN setup without a bundler:
<script src="https://unpkg.com/@zxing/[email protected]"></script>
<script src="https://unpkg.com/@zxing/[email protected]"></script>You will also need a <video> element in the DOM to display the camera feed:
<video id="scanner" style="width: 100%; max-width: 640px;"></video>
<div id="result"></div>Core Features
1. Decode a QR Code from an Image
The simplest use case takes a static image and extracts any barcode or QR code from it.
import { BrowserQRCodeReader } from '@zxing/browser';
const reader = new BrowserQRCodeReader();
const img = document.getElementById('qr-image');
// decode from an existing img element
const result = await reader.decodeFromImageElement(img);
console.log('Decoded:', result.getText());This works with any standard image format — PNG, JPEG, or WebP. The reader scans the entire image for recognizable patterns.
2. Live Camera Scanning
Real-time scanning from a webcam or phone camera is the flagship feature. BrowserQRCodeReader handles device enumeration and stream setup.
import { BrowserQRCodeReader } from '@zxing/browser';
const reader = new BrowserQRCodeReader();
const videoEl = document.getElementById('scanner');
// start continuous scanning
const controls = await reader.decodeFromVideoDevice(
undefined, // use default camera
videoEl,
(result, error) => {
if (result) {
document.getElementById('result').textContent = result.getText();
controls.stop(); // stop after first successful read
}
}
);Passing undefined as the device ID selects the default camera. On phones, that is usually the rear camera. The callback fires on every frame where decoding is attempted — result is non-null only when something is found.
3. Multi-Format Barcode Support
ZXing supports more than just QR codes. Switch to BrowserMultiFormatReader to scan EAN-13, Code 128, DataMatrix, PDF417, and others in a single pass.
import { BrowserMultiFormatReader } from '@zxing/browser';
const reader = new BrowserMultiFormatReader();
const controls = await reader.decodeFromVideoDevice(
undefined,
videoEl,
(result, error) => {
if (result) {
// result.getBarcodeFormat() tells you which format matched
console.log('Format:', result.getBarcodeFormat());
console.log('Value:', result.getText());
}
}
);The multi-format reader cycles through all enabled decoders on each frame. This adds a small amount of processing time per frame but covers the widest range of codes.
4. Selecting Front or Back Camera
On mobile devices, you often need the back camera for scanning. List available devices first, then pick the right one.
import { BrowserCodeReader } from '@zxing/browser';
// list all video input devices
const devices = await BrowserCodeReader.listVideoInputDevices();
console.log('Available cameras:', devices);
// typically the back camera label contains 'back' or 'environment'
const backCam = devices.find(d => /back|environment/i.test(d.label));
const controls = await reader.decodeFromVideoDevice(
backCam?.deviceId,
videoEl,
callback
);5. Continuous Scanning with Callback
For applications that need to scan multiple codes in a session — like a checkout line or inventory audit — keep the scanner running and process each result.
const scannedCodes = new Set();
const controls = await reader.decodeFromVideoDevice(
undefined,
videoEl,
(result, error) => {
if (result) {
const text = result.getText();
if (!scannedCodes.has(text)) {
scannedCodes.add(text);
addToList(text); // your UI update function
}
}
}
);
// stop scanning when done
document.getElementById('stop-btn').onclick = () => controls.stop();The Set deduplicates scans — live scanning often reads the same code on consecutive frames.
6. Decoding from File Input
Let users upload an image containing a barcode instead of using a live camera. This covers desktop scenarios where a webcam might not be available.
const fileInput = document.getElementById('file-input');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
const url = URL.createObjectURL(file);
const img = new Image();
img.src = url;
await img.decode();
const result = await reader.decodeFromImageElement(img);
console.log('File scan result:', result.getText());
URL.revokeObjectURL(url);
});7. Configuring Decode Hints
Narrow down which formats to scan if you know what to expect. Fewer formats means faster decoding per frame.
import { DecodeHintType, BarcodeFormat } from '@zxing/library';
import { BrowserMultiFormatReader } from '@zxing/browser';
const hints = new Map();
hints.set(DecodeHintType.POSSIBLE_FORMATS, [
BarcodeFormat.QR_CODE,
BarcodeFormat.EAN_13
]);
hints.set(DecodeHintType.TRY_HARDER, true);
const reader = new BrowserMultiFormatReader(hints);TRY_HARDER tells the decoder to spend more time per frame, which improves accuracy for damaged or low-contrast codes at the cost of speed.
Common Pitfalls
Camera Permissions and HTTPS
Browsers require HTTPS (or localhost) to access getUserMedia. If your dev server runs on plain HTTP over a network IP, the camera API will be blocked entirely — no prompt, just a silent rejection.
Stopping the Scanner
Forgetting to call controls.stop() is a common source of memory leaks. The video stream stays open and the decoder keeps processing frames in the background. Always stop scanning when navigating away or closing a modal.
Mirror Mode on Front Camera
Front cameras produce a mirrored image by default. ZXing still decodes correctly from mirrored input, but if you overlay a scanning rectangle or UI indicator, the coordinates will be flipped. Apply transform: scaleX(-1) to the video element if you want a mirror view, but keep the raw stream unmirrored for decoding.
Low Light and Auto-Focus
Phone cameras often struggle with QR codes in dim environments. You cannot control the flashlight through the ZXing API directly. Instead, access the torch through the MediaStream track capabilities:
const track = videoEl.srcObject.getVideoTracks()[0];
await track.applyConstraints({ advanced: [{ torch: true }] });This only works on devices that expose torch control — mainly Android Chrome.
Bundler Configuration
Some bundlers tree-shake aggressively and remove barcode format classes that appear unused. If you get runtime errors about missing decoders, check that your bundler is not stripping the format modules. Webpack users may need to add @zxing to the transpile include list.
Alternatives Comparison
| Feature | @zxing/library | html5-qrcode | QuaggaJS | jsQR |
|---|---|---|---|---|
| QR Code | Yes | Yes | No | Yes |
| 1D Barcodes | Yes (12+ formats) | Yes | Yes | No |
| DataMatrix | Yes | No | No | No |
| Camera Integration | @zxing/browser | Built-in | Built-in | Manual |
| Bundle Size | ~180 KB | ~120 KB | ~190 KB | ~50 KB |
| Active Maintenance | Moderate | Active | Dormant | Dormant |
| License | MIT | Apache 2.0 | MIT | Apache 2.0 |
html5-qrcode is a strong alternative if you only need QR and a few 1D formats — it has a simpler API and active maintenance. jsQR is lightweight but limited to QR-only and requires you to handle the camera yourself. QuaggaJS excels at 1D barcodes but has not received updates recently. ZXing remains the most comprehensive option for multi-format scanning.
References
The official GitHub repository contains detailed API documentation and migration guides for version upgrades. The npm packages are published under the @zxing scope with separate modules for the core library and browser integration layer.