A smartphone scanning a barcode on a shipping box in a warehouse, with a gray-blue cat watching calmly from a nearby desk.

STRICH: Production-Grade Barcode Scanning Without Leaving the Browser

The Gray Cat
The Gray Cat
1 view

Barcode scanning in the browser sounds like a solved problem until you actually ship it. The happy-path demo works on your brand-new iPhone in a well-lit office. Then a warehouse worker points a battered Android at a crumpled shipping label under fluorescent flicker, and your scanner just sits there blinking. Recognition quality, autofocus behavior, motion blur, and low-end cameras are where most browser scanners quietly fall apart.

@pixelverse/strichjs-sdk, marketed as STRICH, is a JavaScript SDK built specifically for that hostile environment. It turns a device camera into a fast, robust scanner for 1D and 2D barcodes, running entirely client-side on top of WebAssembly, WebGL, and the browser camera APIs. No frames leave the device, no native app is required, and the whole thing drops into a web app, a PWA, or a web view inside an existing mobile app. It is the kind of tool you reach for when you are building internal logistics, retail, ticketing, or field-service software and need scanning that actually holds up in the field.

One honest caveat before we go any further: STRICH is commercial software. The npm package is published under a commercial license, and production use requires a paid license key. There is a free trial tier and dev keys work on localhost, so you can evaluate it without spending anything, but this is not an MIT library you can ship to production for free. We will come back to whether that tradeoff is worth it.

Why Reach for a Paid Scanner

The browser barcode space already has free options: ZXing, html5-qrcode, and Quagga2 are all open source and widely used. So why pay?

The answer is almost entirely about the messy middle 20% of real-world conditions. The free libraries handle a clean QR code on a bright screen just fine. STRICH is engineered for the cases that matter in production:

  • Recognition robustness under poor lighting, glare, motion, damaged labels, and curved or rounded surfaces.
  • Breadth of symbologies, including the awkward retail and logistics formats that free libraries often skip or implement poorly.
  • Performance from a tuned WebAssembly engine rather than pure JavaScript pixel-pushing.
  • A small, clean API with official sample apps for React, Angular, Vue, and vanilla JavaScript.

It runs 100% on-device, which is also a genuine privacy story: camera frames are processed in WASM locally and never uploaded anywhere. For an internal tool handling order numbers or patient IDs, "nothing leaves the phone" is a real selling point.

The supported symbology list is broad. On the 1D side you get EAN-13/EAN-8, UPC-A/UPC-E, Code 128, Code 39, Code 93, ITF, GS1 DataBar (including DataBar Limited), Codabar, MSI Plessey, and PDF417. On the 2D side it covers QR Code (including Micro QR Code), Data Matrix (including the rectangular variants), and Aztec Code. That coverage spans retail receipts, shipping labels, boarding passes, and the tiny Data Matrix codes etched onto small electronic parts.

Getting It Into Your Project

STRICH installs from npm like anything else:

npm install @pixelverse/strichjs-sdk
yarn add @pixelverse/strichjs-sdk

If you prefer not to bundle it, you can also import it dynamically from a CDN, which is handy for quick prototypes:

import { StrichSDK, PopupScanner } from "https://cdn.jsdelivr.net/npm/@pixelverse/strichjs-sdk@latest";

The package ships TypeScript types out of the box, so you get full editor autocompletion with no extra @types package. Two things to keep in mind before you start: STRICH requires HTTPS because it depends on the camera APIs, and license keys are URL-scoped. The good news is that localhost and private IP ranges are automatically allowed, so local development just works once you have a trial key from the customer portal.

Your First Scan With the Popup Scanner

The fastest way to feel the SDK working is the PopupScanner, a one-call modal scanner. You initialize the SDK once with your license key, then call scan() and await the result.

import { StrichSDK, PopupScanner } from "@pixelverse/strichjs-sdk";

async function scanOnce() {
  // Initialize the SDK exactly once for the lifetime of the page.
  await StrichSDK.initialize("<your license key>");

  // Open a modal scanner restricted to the codes you care about.
  const detections = await PopupScanner.scan({
    symbologies: ["qr", "code128"],
  });

  if (detections) {
    console.log(`Scanned: ${detections[0].data}`);
  }
}

PopupScanner.scan() opens a full modal, lets the user line up a barcode, and resolves with an array of detections (or nothing if the user dismisses it). Each detection exposes the decoded payload as .data. Under the hood the popup uses the native HTML <dialog> element, so it needs a browser that supports it, which today means essentially all of them.

Notice the symbologies array. Restricting the scanner to the exact code types you expect is not just tidiness, it genuinely speeds up recognition and cuts down on false reads. If you only ever scan Code 128 shipping labels, say so, and the engine stops wasting cycles looking for QR codes.

Embedding a Live Scanner

The PopupScanner is great for "scan one thing and return," but most real apps want a scanner embedded directly in their own UI, with custom framing, a persistent viewfinder, or continuous scanning. That is what the BarcodeReader is for.

You give it a configuration object pointing at a host DOM element, initialize it, wire up the detected callback, and start it:

import { BarcodeReader } from "@pixelverse/strichjs-sdk";

const config = {
  selector: "#scanner", // host element, must use position: relative
  engine: {
    symbologies: ["code128"],
    duplicateInterval: 2500, // ms before the same code fires again
  },
};

const reader = await new BarcodeReader(config).initialize();

reader.detected = (detections) => {
  // detections is an array; .data holds the decoded string
  console.log(detections[0].data);
};

await reader.start();

The selector points at a container element that must be styled position: relative, because STRICH overlays the live camera view and its UI inside it. The duplicateInterval is a small but important ergonomic detail: while a barcode stays in frame, it would otherwise fire the callback dozens of times per second. Setting a duplicate interval means the same physical code is only reported once every 2.5 seconds, which is usually what you want for a continuous scanning workflow.

The lifecycle methods are deliberately minimal. initialize() acquires the camera stream and prepares the reader, start() begins recognition, stop() suspends frame processing without releasing the camera, and destroy() tears everything down and releases the camera entirely. That last one matters more than it looks, which brings us to React.

Wiring STRICH Into React the Right Way

React is where the camera-cleanup discipline becomes essential. The pattern is: initialize the SDK once for the whole app, render a host element, and manage the BarcodeReader lifecycle inside an effect that always tears the reader down on cleanup.

import { useEffect, useRef } from "react";
import { StrichSDK, BarcodeReader } from "@pixelverse/strichjs-sdk";

let sdkInitialized = false;

async function ensureSdk(licenseKey: string) {
  if (!sdkInitialized) {
    await StrichSDK.initialize(licenseKey);
    sdkInitialized = true;
  }
}

export function Scanner({ licenseKey }: { licenseKey: string }) {
  const hostRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let reader: BarcodeReader | undefined;
    let cancelled = false;

    (async () => {
      await ensureSdk(licenseKey);
      if (cancelled || !hostRef.current) return;

      reader = await new BarcodeReader({
        selector: `#${hostRef.current.id}`,
        engine: { symbologies: ["qr", "ean13"], duplicateInterval: 2000 },
      }).initialize();

      reader.detected = (detections) => {
        console.log("Scanned:", detections[0].data);
      };

      await reader.start();
    })();

    return () => {
      cancelled = true;
      // Release the camera no matter what. Forgetting this leaves the
      // camera light on and can block the next initialize().
      reader?.destroy();
    };
  }, [licenseKey]);

  return <div id="strich-host" ref={hostRef} style={{ position: "relative" }} />;
}

Two details earn their keep here. First, the module-level sdkInitialized guard ensures StrichSDK.initialize() runs exactly once even though React 18's Strict Mode deliberately mounts effects twice in development. Second, the cleanup function always calls destroy(). If you skip that, the camera light stays on after the component unmounts and a subsequent reader may fail to acquire the camera. The cancelled flag guards against the double-invoke race where the async setup is still in flight when cleanup runs.

This is the same shape STRICH uses in its official React sample repository, and it generalizes cleanly to Angular and Vue: initialize once, embed against a relatively-positioned host, and tear down on teardown.

Torch, Zoom, and Other Field Niceties

Real scanning happens in bad lighting and at awkward distances, so STRICH exposes the hardware controls that make a scanner usable in practice. The BarcodeReader instance lets you toggle the device flashlight and adjust zoom programmatically, where the device supports it:

// Light up a dim shelf
reader.setFlashlight(true);

// Hide or show the reader without destroying it
reader.setVisible(false);

You can also hook an onError callback to handle unexpected processing errors gracefully rather than letting them surface as uncaught rejections. Combined with the animated overlay controls and zoom added in recent releases, this gives you enough surface area to build a polished scanning experience without dropping down to raw getUserMedia plumbing yourself.

The project has stayed actively maintained, with a steady cadence of releases through 2026. Recent versions rewrote the PDF417 decoder for better recognition and runtime performance, added Micro QR Code and rectangular Data Matrix support, and introduced DataBar Limited, so the symbology coverage and recognition quality keep improving rather than sitting still.

How It Compares

To set expectations honestly, here is roughly where STRICH sits among browser scanners. html5-qrcode and ZXing (@zxing/library, @zxing/browser) are the popular free, Apache-licensed options; they are great for clean QR and common 1D codes but trade away recognition robustness in tough conditions and breadth of symbology support. Quagga2 (@ericblade/quagga2) is MIT-licensed and focused mainly on 1D barcodes, with no real 2D story. At the high end, Scandit is the enterprise incumbent with excellent recognition and premium pricing.

STRICH aims squarely at the middle: commercial-grade recognition and wide symbology coverage with a small, clean API and official framework samples, positioned as a leaner and more affordable alternative to the enterprise heavyweight. The tradeoff is the one we opened with: you are paying for a license rather than reaching for something free and open. Whether that pays off comes down to a simple question. If your scanner only ever meets clean codes in good light, a free library is probably fine. If it has to work reliably in a warehouse, a clinic, or the back of a delivery van, the field reliability, vendor support, and symbology breadth are exactly what the license buys.

Wrapping Up

STRICH solves a problem that is deceptively hard: scanning real barcodes, in real conditions, from a web app, without shipping a native binary. The API is small and pleasant, the WebAssembly engine is built for the cases that break lesser scanners, and everything runs on-device so your camera data never leaves the phone. The PopupScanner gets you scanning in a handful of lines, and the BarcodeReader gives you full control when you need to embed a live viewfinder and manage its lifecycle.

The commercial license is the obvious thing to weigh, and you should weigh it honestly rather than assuming paid equals better for your specific use case. But for production scanning where reliability is the whole point, STRICH is a genuinely strong option that lets you stay in the browser and out of the app stores. Grab a trial key, drop the React component above into a page, and point your camera at the nearest shipping label to see whether it earns its place in your stack.