Adding a barcode scanner to a web app is oddly painful. The popular open-source libraries are either QR-only or barely maintained, and the enterprise SDKs start in the five figures. Yet modern browsers can open the camera, and WebAssembly is fast enough to decode frames on-device — so a real scanner is closer than you'd think.
In this tutorial we'll build a working camera barcode scanner in about 15 lines of JavaScript. It reads EAN, UPC, Code 128 and QR straight from the camera, on desktop and mobile, with nothing leaving the browser.
Disclosure: I built BarqScan, the SDK we'll use. It wraps the battle-tested ZXing-C++ decoder with the camera pipeline and accuracy tuning you'd otherwise write yourself. The decoding runs entirely on-device.
What we're building
A page that opens the camera, scans barcodes, and logs each result with its symbology and decode time. That's it — no backend.
Prerequisites
- A secure context:
https://in production orhttp://localhostin development. Browsers only grant camera access on secure origins. - Camera permission — the browser prompts automatically the first time.
Step 1 — Install
npm install @barqscan/web```
{% endraw %}
No build step? Drop in the hosted script tag instead:
{% raw %}
```html
<script src="https://barqscan.com/sdk/v1/index.global.js"></script>
Step 2 — Add a container
The scanner mounts into an element you provide. Give it a size and an aspect ratio:
<div id="scanner" style="width: 640px; aspect-ratio: 4/3"></div>
Step 3 — Configure and mount the scanner
import * as BarqScan from "@barqscan/web";// Free on localhost — use any placeholder key while developing.
await BarqScan.configure("BARQ-XXXXX-XXXXX-XXXXX-XXXXX");
const scanner = await BarqScan.ScannerView.create(
document.getElementById("scanner"),
{
symbologies: ["ean13", "code128", "qr"],
duplicateFilter: 3000, // ignore the same code for 3s
playSoundOnScan: true,
},
);
ScannerView.create() picks the back camera, requests 1080p with continuous autofocus, and starts decoding the viewfinder region at native resolution.
Step 4 — Handle results
Subscribe to the scan event. Each fired event carries the barcodes found in that frame:
scanner.on("scan", ({ barcodes, decodeTimeMs }) => {
for (const b of barcodes) {
console.log(`${b.symbology}: ${b.data} (${decodeTimeMs.toFixed(0)}ms)`);
}
});
Every result gives you a symbology (e.g. "ean13") and the decoded data string.
Using it in React
The one thing to remember in a component is cleanup — release the camera when the component unmounts:
import { useEffect, useRef } from "react";
import * as BarqScan from "@barqscan/web";export function Scanner({ onScan }) {
const ref = useRef(null);
useEffect(() => {
let scanner;
(async () => {
await BarqScan.configure("BARQ-XXXXX-XXXXX-XXXXX-XXXXX");
scanner = await BarqScan.ScannerView.create(ref.current, {
symbologies: ["ean13", "code128", "qr"],
});
scanner.on("scan", ({ barcodes }) => onScan(barcodes[0]?.data));
})();
return () => scanner?.destroy(); // release the camera on unmount
}, [onScan]);
return <div ref={ref} style={{ width: "100%", aspectRatio: "4 / 3" }} />;
}
Lifecycle & production notes
-
scanner.pause()/scanner.resume()— stop and continue decoding without dropping the camera. -
scanner.destroy()— release the camera and DOM entirely. - For production, self-host the WebAssembly by passing a
wasmUrltoconfigure()instead of the default CDN. - Because decoding is on-device, there's no server round-trip: it works offline and camera frames never leave the user's device.
- Licensing is per-domain, and development on
localhostis free.
Wrap-up
That's a full camera barcode scanner in ~15 lines, running entirely in the browser. You can try the live demo with your own camera (no signup), or read the docs for the full API.
If you've fought with barcode scanning on the web before, I'd love to hear it — which symbologies or frameworks should I cover next?
Top comments (0)