Developers & agents
Read QR codes from a webcam in the browser
Use the built-in BarcodeDetector API where it exists (Chromium browsers), and fall back to zxing-wasm elsewhere — Firefox and Safari do not ship BarcodeDetector. Open the camera with getUserMedia using facingMode environment, then decode frames in a requestAnimationFrame loop. Everything runs locally; no frame ever needs to leave the device.
Two decoders, one interface
The platform has a native decoder — BarcodeDetector — but only in Chromium-based
browsers (Chrome and Edge on Android, ChromeOS and macOS, per current compatibility
data). Firefox and Safari do not ship it, so a real implementation is always
feature-detect plus fallback. The good news: zxing-wasm exposes a nearly identical
detect-from-bitmap call, so the fallback is a few lines, not a rewrite.
async function makeDetector() {
if ("BarcodeDetector" in window) {
const formats = await BarcodeDetector.getSupportedFormats();
if (formats.includes("qr_code")) {
const native = new BarcodeDetector({ formats: ["qr_code"] });
return (source) => native.detect(source);
}
}
const { readBarcodes } = await import("zxing-wasm/reader");
return async (source) => {
const bitmap = await createImageBitmap(source);
const results = await readBarcodes(bitmap, { formats: ["QRCode"] });
return results.map((r) => ({ rawValue: r.text }));
};
}
Note the double check: some browsers expose the constructor but not the qr_code
format, so query getSupportedFormats() rather than trusting in window.
The camera and the scan loop
const video = document.querySelector("video");
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: "environment", width: { ideal: 1280 } },
});
video.srcObject = stream;
await video.play();
const detect = await makeDetector();
async function tick() {
const codes = await detect(video);
if (codes.length > 0) {
handle(codes[0].rawValue); // decoded text
stream.getTracks().forEach((t) => t.stop());
return;
}
requestAnimationFrame(tick);
}
tick();
Three constraint choices that matter:
facingMode: "environment"selects the rear camera on phones — the front camera mirrors the image and sits at the wrong distance for scanning.width: { ideal: 1280 }is enough: a QR module needs only a few pixels to decode, and 4K frames just slow the loop down. See module size vs camera resolution.- Decode at most once per animation frame. Running the detector on every frame of a 60 fps stream wastes battery for no extra hits; every second or third frame is fine.
Torch, focus, permissions
On Android Chrome you can often light the scene: check
track.getCapabilities().torch and, if true,
track.applyConstraints({ advanced: [{ torch: true }] }). iOS Safari does not expose
the torch to web pages. Continuous autofocus is the default on phone cameras; there is
no reliable cross-browser way to force focus, so if scanning fails, moving the code to
15–30 cm from the lens helps more than any constraint.
getUserMedia requires a secure context — HTTPS or localhost — and a permission
prompt. Handle denial gracefully: offer a file-input fallback
(<input type="file" accept="image/*" capture>) and decode the chosen photo through
the same detector function, since both accept image sources.
Privacy is the point
This entire pipeline runs on-device. No frame is uploaded, which matters when the code in front of the camera is a WiFi password, a payment code or a vCard. UseQR's own scanner is built exactly this way — camera and image decoding, fully client-side — and the same argument applies to generation: client-side vs server-side. If you need to decode a stored image server-side instead, that is a different pattern.
FAQ
How do I scan a QR code with JavaScript in the browser?
Feature-detect BarcodeDetector, fall back to zxing-wasm, open the rear camera with getUserMedia and call the detector on the video element in a requestAnimationFrame loop. The whole implementation is around 30 lines.
Which browsers support the BarcodeDetector API?
Chromium-based browsers — Chrome and Edge on Android, ChromeOS and macOS. Firefox and Safari do not support it, so production code always needs a WebAssembly fallback such as zxing-wasm.
Does browser QR scanning upload the camera feed?
No. Both BarcodeDetector and zxing-wasm decode frames locally on the device. No image leaves the browser unless your code explicitly uploads one, which makes this approach suitable for sensitive payloads.
Why does my webcam QR scanner fail to focus?
Laptop webcams are fixed-focus and phone cameras autofocus continuously; neither can be forced reliably from the web. Hold the code 15–30 cm from the lens, fill a quarter of the frame, and make sure the quiet zone is visible.
Try it — free, no signup
Related
- Decode a QR code programmatically — Read QR codes from images in code — POST bytes or a URL to the keyless decode API, or decode locally with zxing-cpp, zbar or pyzbar. Snippets included.
- Generate a QR code in JavaScript and React — For a QR code in a browser or React app, either point an <img> at the keyless API — one line, no dependency — or use a client-side library such as qrcode…
- QR code libraries compared — A map of the QR library ecosystem — generation and decoding, by language, with licences and honest maintenance status. Pick by use case, not by stars.
- A free QR code API with no key — UseQR's REST API needs no signup, no API key and no SDK. GET /api/v1/qr?data=hello returns a PNG. The shortest form is /q/hello.png, which drops straight…