Skip to content
UseQR
ESC

Jump to

MOVEOPEN50 places

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.

View as MarkdownPaste this page into any AI assistant. It is plain, portable Markdown.

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

  • 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…