JavaScript Barcode Detector Table

PieceWhat it doesField note
BarcodeDetector.getSupportedFormats()The platform checkthe feature detect: empty or thrown means no native decoder - branch to your library fallback there and never again
new BarcodeDetector({formats})The scoped decoderformats is hint and filter at once: qr_code only skips a dozen decode passes, roughly doubling scan rate on busy frames
detector.detect(source)The one-call scanaccepts img, video, canvas, Blob, ImageData; returns decoded values - the whole file-picker scan feature is this one await
result.rawValue + formatThe payloadrawValue is attacker-controlled input: show it before opening, verify signatures in business flows, never auto-navigate
result.cornerPointsThe geometryfour corners in source pixels - scale by display ratio and you have the targeting overlay for free
the live loopThe camera scansetTimeout at 10fps over a playing getUserMedia video beats rAF-per-frame on battery; stop on hidden and on first hit
the dedupe cooldownThe noise gatesame rawValue within 2 seconds is noise, not a scan - the classic bug is a scanner that keeps vibrating after success
the support truthThe matrixChromium-only today (Shape Detection family); no Safari/Firefox - wrap native and WASM decoders in one results interface
Reference: the MDN Barcode Detection API. QR and barcode decoding as a native call: hand it any paintable image source, get values, symbologies and corner geometry back - no library where the platform has one, and a clean seam for a decoder where it does not.
Bottom line: detect once with getSupportedFormats, branch once at startup, keep the scanner stack (camera loop, overlay, dedupe, UX) identical above the decoder seam. The three-line happy path is real - construct with formats, await detect, draw cornerPoints - and the failure modes are all source-side: paused videos scan as silence, cross-origin taint blocks detection, EXIF rotation flips photos.
Related tools: the getUserMedia table (the camera behind the loop), the canvas table (the overlay drawing), the Blob table (the taint-free file source), the offscreen canvas table (worker-side frame prep), and the web workers table (the fallback decoder off the main thread).

The Barcode Detection API turns QR and barcode scanning from a JavaScript marathon into a native call: hand the detector an image source - an img, a canvas, a video frame, a Blob, ImageData - and it returns the decoded contents with bounding boxes and corner points. No library, no WASM bundle, no ZXing port: the platform decoder does the work, which is why it is fast and why it is unevenly distributed.

Bottom line: the flow is three lines - const detector = new BarcodeDetector({formats: ['qr_code']}); const results = await detector.detect(imageSource); - and each result carries rawValue, format, and cornerPoints (the four corners, in image coordinates, which is everything you need to draw a targeting overlay). The formats array is a performance hint and a filter at once: restricting to qr_code skips decoding passes for the dozen other symbologies, which roughly doubles scan rate on busy frames.

The honest part: this is a Chromium-platform API today (Chrome, Edge, Android browsers via the Shape Detection family) with no Safari or Firefox implementation, so a production scanner needs a fallback library behind the feature detect - and the fallback choice (a JS/WASM decoder) is exactly what the native path lets you skip where it exists. Detect once with BarcodeDetector.getSupportedFormats(), branch once at startup, and keep the rest of your scanner (camera loop, overlay, UX) identical across both paths.

How to use

  1. Check the platform first: const formats = await BarcodeDetector.getSupportedFormats(); - an empty or thrown result means no native decoder; branch to your library fallback there and never again.
  2. Construct with intent: const detector = new BarcodeDetector({formats: ['qr_code', 'ean_13']}); - only the symbologies you actually accept; a bare constructor detects everything and pays for it per frame.
  3. Scan any static source: const codes = await detector.detect(imgOrCanvasOrBlobOrImageData); - works on anything paintable; for a file-picker flow (scan a photo) this is the whole feature: one call, draw cornerPoints on a canvas overlay.
  4. Build the live loop: requestAnimationFrame or setTimeout over video frames - grab the video element (playing, with getUserMedia camera behind it) and call detector.detect(video) each tick; the API reads the current frame, nothing to draw first.
  5. Use the geometry: results.forEach(r => drawBox(r.cornerPoints, r.rawValue)) - cornerPoints are in source-image pixels, so scale by the display/canvas ratio before overlaying; rawValue is the decoded string, format the symbology, and remember QR results may be URLs the user should see before opening.

Frequently asked questions

Which image sources work with detect(), and which fail silently?

The spec accepts anything ImageBitmapSource: HTMLImageElement, SVG image, HTMLVideoElement, HTMLCanvasElement, OffscreenCanvas, ImageBitmap, ImageData, Blob, and plain ImageData-shaped objects. In practice two traps dominate. First, video: the video must be actively playing with real frames - detect() on a paused or metadata-only video returns an empty array without error, which reads as the scanner being broken when it is actually the source. Second, cross-origin taint: an img or canvas that drew cross-origin content without CORS taints the source, and detection on tainted sources throws or returns empty depending on engine - the same rule that blocks getImageData, so the fix is the same too: crossorigin attributes and proper CORS headers on image hosts, or same-origin/Blob URLs. Blob sources are the safe universal choice for file uploads: decodeImage to a Blob via canvas.toBlob and you sidestep taint entirely. And note orientation: EXIF-rotated JPEGs from phone cameras may decode rotated in some engines - if photos fail to scan, redraw the image to a canvas with correct orientation and scan that.

How do I build a live camera scanner that does not melt the battery?

Three budgets decide it: frame rate, resolution, and detection scope. Frame rate: the detector is fast (often under 10ms per frame on modern phones) but not free - a 10fps setTimeout loop outperforms rAF-per-frame for battery with no user-visible difference, because barcodes do not move fast. Resolution: request the camera at 1280x720 with facingMode: 'environment' - 4K frames slow both the copy and the decode, and QR tolerates modest resolution fine; zoom in optically (digital zoom crops the frame) only when codes are small. Scope: pass formats in the constructor so each frame skips a dozen decoder passes, and stop the loop entirely when the tab is hidden (visibilitychange) and when a code is found - the classic bug is a scanner that keeps scanning after success, vibrating on every duplicate. Deduplicate by rawValue with a cooldown: same value within 2 seconds is noise, not a new scan.

What do I do when the API is missing - Safari, Firefox, older Chrome?

Branch once at startup and keep the whole scanner stack identical above the decoder. The feature detect is await BarcodeDetector.getSupportedFormats() inside try/catch (constructing without the guard throws on unsupported engines); on failure, load your fallback decoder - a WASM port of ZXing or similar - lazily, only when the scanner view opens, so the library cost is paid by scanner users only. The seam that makes this clean: your scanner component speaks in image sources and expects results shaped {rawValue, format, cornerPoints}; wrap the native detector and the library in that same interface, and the camera loop, overlay drawing, dedupe and UX never learn which decoder ran. Two platform notes from the field: iOS Safari needs the fallback today, and fallback decoders are slower per frame - lower the loop rate and prefer higher-resolution single-shot scans (take a frame, decode, retry) over continuous video scanning when the fallback path is active.

Is this secure? What are the real-world gotchas with scanned content?

The API itself is safe - it reads images you already have and grants no camera access by itself (the camera comes from getUserMedia with its usual permission prompt) - but the content is attacker-controlled input like any other. QR codes are a delivery mechanism for URLs, and auto-navigating on a successful scan is the classic phishing funnel: show the decoded value, require a tap to open, and warn on non-http schemes (javascript: does not navigate from location assignment, but custom schemes and shorteners obfuscate destinations). Validate length - a 4KB QR payload is not a URL, it is a payload smuggling attempt for your text field. And when scanning for business flows (tickets, pairing codes), verify a signature or nonce server-side: rawValue is trivially forgeable by anyone who can print a code. One engine quirk worth knowing: some Android OEM builds return duplicates across frames even within the cooldown - dedupe on your side, never trust the detector to be stateless-lazy in your favor.

Related tools