Skip to content
Shiny.NET
Commercial SupportGuaranteed response times on Shiny bug fixes, straight from the maintainers. Plans from US$1,500 a year.See the plans

Barcodes

NuGet package Shiny.AppDeviceBridge.Barcodes

The barcodes bridge turns the device’s camera into a barcode scanner for the page. When the page asks, the device shows a full-screen scanner over the web app, and the codes it reads come back to the page. It’s built on the modal scanner in Shiny Controls’ media service, including its long-range mode, which reads codes the platform scanners miss, such as a shelf label across an aisle, and can zoom in on them by itself.

This is the camera of the device running the app. A page that wants the camera of the machine showing it uses the browser’s own BarcodeDetector. To drive the device’s camera from somewhere else, with a viewfinder, see Device Camera.

Platform Reads codes with
iOS, Mac Catalyst, macOS Apple Vision
Android ML Kit
Windows ZXing.Net
Linux nothing: every route answers 501

LongRange mode works on every platform except Linux. Its own decoder reads the 1D codes, and the platform’s scanner reads the rest.

Terminal window
dotnet add package Shiny.AppDeviceBridge.Barcodes
builder.UseAppDeviceBridge(
bridge => bridge.AddBarcodesBridge(),
webApp => { /* … */ }
);

AddBarcodesBridge() calls UseShinyCamera() for you. Declare the camera permission:

  • iOS and Mac Catalyst: NSCameraUsageDescription in Info.plist. On macOS, also the camera entitlement when sandboxed.
  • Android: <uses-permission android:name="android.permission.CAMERA" />.
  • Windows: the webcam capability.

The scanner asks for the permission itself the first time. A refusal comes back as reason: "PermissionDenied", not as an error.

import { BarcodesBridge } from "@shinyorg/appdevicebridge";
const barcodes = new BarcodesBridge();
// one code, of any kind
const { barcodes: [code], reason } = await barcodes.scan({});
if (code)
lookup(code.value, code.format);

scan answers when the scanner closes, with every code it read and why it closed. In Blazor, register AddBarcodesBridgeClient() and inject IBarcodesBridge.

maxResults: 0 keeps the scanner open until the person taps ✓, timeoutSeconds passes without a read, or the page calls stopScan(). Listen for barcodes.detected to show each code as it’s read:

const off = await barcodes.onDetected(code => addRow(code.value));
const result = await barcodes.scan({
maxResults: 0,
formats: ["Ean13", "UpcA"],
instructions: "Scan each item, then tap ✓"
});
await off();

filterDuplicates (on by default) reports each symbology and value once per scan.

await barcodes.scan({
mode: "LongRange",
autoZoom: true,
formats: ["Ean13", "UpcA", "Code128"]
});

In Standard mode, the platform’s scanner reads the whole frame, every frame. That’s the quickest read of a code held up to the lens. But Vision and ML Kit shrink the frame before they look at it, so a code a few metres away loses its bars. LongRange mode finds codes in the full-resolution frame first. It reads 1D codes (EAN/UPC, Code 128, Code 39, ITF) with a blur-tolerant decoder down to about a pixel per bar. 2D codes go to the platform’s scanner cut out and enlarged, and codes are tracked across frames. With autoZoom, a code that’s been found but is too small to read pulls the camera in until it can be read. A pinch takes back control at once. A big, close code can read a frame or two later than in Standard mode, so Standard is still the default.

Narrowing formats is the biggest speed-up. A list with only 1D formats skips the platform’s scanner altogether.

The remaining settings tune long range and are ignored in Standard mode. Leave them out to use the scanner’s defaults:

Field Range Default Meaning
maxAutoZoom 1–10 4 The furthest auto-zoom goes, as a multiple of the starting zoom
fullFrameInterval 0–30 3 Run the platform’s scanner over the whole frame every this many frames, to catch close codes and Code 93 / Codabar. 0 never does
maxNativeCropsPerFrame 0–8 2 Candidates handed to the platform’s scanner per frame, cut out and enlarged
uncheckedConfirmations 1–10 2 Frames a code without a check digit (Code 39, short ITF) must read on before it’s reported

Every field is optional. {} reads one code of any kind and waits as long as the person takes.

Field Default Meaning
formats all QrCode, Aztec, DataMatrix, Pdf417, Code128, Code39, Code93, Codabar, Ean8, Ean13, UpcA, UpcE, Itf
mode Standard Standard or LongRange
autoZoom false LongRange only: zoom in on codes too small to read
maxResults 1 Close after this many codes. 0 keeps scanning until the person, the timeout or the page ends it. At most 1000
filterDuplicates true Report each symbology and value once per scan
timeoutSeconds none Close after this long without a read. Each code restarts the clock. At most 3600
scanWindow whole frame { x, y, width, height } from 0 to 1. Only read codes inside it. The scanner shows it as an aiming guide
torchOn false Start with the torch lit, where there is one
title, instructions none Shown on the scanner. At most 256 characters each

{ barcodes, reason }. Each code has its value, its format, and its bounds in the upright camera image (0 to 1).

reason Meaning
MaxResults maxResults codes were read. A one-code scan that finds one ends this way
Done The person tapped ✓
Dismissed The person closed the scanner
Timeout Nothing was read within timeoutSeconds
Stopped The page called stopScan()
PermissionDenied The camera permission was refused, so the scanner never opened
CameraUnavailable The app had no window to show the scanner on

Aborting the request (an AbortSignal, or a cancelled token in C#) closes the scanner.

Status Code When
400 bad_request A field is out of range, formats includes Unknown, or the body isn’t a scan request
409 scan_in_progress Another scan is running. The device runs one at a time
501 not_supported Linux, or the app didn’t register the bridge
GET/_bridge/barcodes#

Whether this platform can scan, and whether a scan is running.

C#IBarcodesBridge.GetStatusAsyncTSBarcodesBridge.getStatus
Response
200 BarcodeScannerStatus
BarcodeScannerStatus · 2 fields
supportedbooleanWhether this platform can scan. False on Linux.
scanningbooleanWhether a scan is running now. The device runs one at a time.
POST/_bridge/barcodes/scan#

Shows the scanner and answers when it closes, with every code it read. A refused permission, a person backing out or a timeout is a result (reason), not an error. Fails with 400 for a request out of range, and with 409 scan_in_progress while another scan is running.

C#IBarcodesBridge.ScanAsyncTSBarcodesBridge.scan
Body
application/json · BarcodeScanRequest
BarcodeScanRequest · 14 fields
formatsBarcodeFormat[] | nullThe symbologies to read; null or empty reads all. Narrowing it is the biggest speed-up.
modeBarcodeScanModeHow frames are read.
autoZoombooleanLongRange only: zoom in on a code that has been found but is too small to read, and zoom back out once nothing is in view. A pinch takes control back at once.
maxAutoZoomnumber | nullLongRange only: the furthest auto-zoom goes, as a multiple of the starting zoom. 1 to 10; the scanner's default is 4.
fullFrameIntervalnumber | nullLongRange only: run the platform's scanner over the whole frame every this many frames, which catches close codes and Code 93 / Codabar. 0 (never) to 30; the scanner's default is 3.
maxNativeCropsPerFramenumber | nullLongRange only: candidates handed to the platform's scanner per frame, cut out and enlarged. 0 to 8; the scanner's default is 2.
uncheckedConfirmationsnumber | nullLongRange only: frames a code without a check digit (Code 39, short ITF) must read on before it is reported. 1 to 10; the scanner's default is 2.
maxResultsnumberClose the scanner after this many codes. 1 by default, a one-code scan; 0 keeps scanning until the person taps done, timeoutSeconds passes or the page stops it. At most 1000.
filterDuplicatesbooleanReport each symbology and value once per scan. On by default.
timeoutSecondsnumber | nullClose the scanner after this long without reading anything; each code restarts the clock. Null waits as long as it takes. At most an hour.
scanWindowBarcodeRegion | nullOnly read codes inside this region, which the scanner also shows as an aiming guide.
torchOnbooleanStart with the torch lit, where there is one.
titlestring | nullShown at the top of the scanner. At most 256 characters.
instructionsstring | nullShown under the title. At most 256 characters.
Response
200 BarcodeScanResult
BarcodeScanResult · 2 fields
barcodesScannedBarcode[]Every code read, in the order it was read. Empty when the scan ended without one.
reasonBarcodeScanEndReasonWhy the scan ended.
Errors
400Bad request409Conflict
DELETE/_bridge/barcodes/scan#

Closes the scanner. The running scan answers with what it read so far and Stopped. Does nothing when no scan is running.

C#IBarcodesBridge.StopScanAsyncTSBarcodesBridge.stopScan
Response
204 No content
EVENTbarcodes.detected#

A code was read, as it happens, for a scan that collects many.

C#IBarcodesBridge.OnDetectedAsyncTSBarcodesBridge.onDetected
Payload
ScannedBarcode
valuestringThe decoded payload.
formatBarcodeFormatThe symbology.
boundsBarcodeRegionWhere it was in the upright camera image.

JSON property names are camelCase and enums travel as their names. A ? marks a property the payload may omit;| null one that may be null.

BarcodeScannerStatusobject#
supportedbooleanWhether this platform can scan. False on Linux.
scanningbooleanWhether a scan is running now. The device runs one at a time.
BarcodeScanRequestobject#

A scan. Every member is optional: an empty request reads the first code of any symbology, in Standard mode, and waits for the person as long as they take.

formats?BarcodeFormat[] | nullThe symbologies to read; null or empty reads all. Narrowing it is the biggest speed-up.
mode?BarcodeScanModeHow frames are read.
autoZoom?booleanLongRange only: zoom in on a code that has been found but is too small to read, and zoom back out once nothing is in view. A pinch takes control back at once.
maxAutoZoom?number | nullLongRange only: the furthest auto-zoom goes, as a multiple of the starting zoom. 1 to 10; the scanner's default is 4.
fullFrameInterval?number | nullLongRange only: run the platform's scanner over the whole frame every this many frames, which catches close codes and Code 93 / Codabar. 0 (never) to 30; the scanner's default is 3.
maxNativeCropsPerFrame?number | nullLongRange only: candidates handed to the platform's scanner per frame, cut out and enlarged. 0 to 8; the scanner's default is 2.
uncheckedConfirmations?number | nullLongRange only: frames a code without a check digit (Code 39, short ITF) must read on before it is reported. 1 to 10; the scanner's default is 2.
maxResults?numberClose the scanner after this many codes. 1 by default, a one-code scan; 0 keeps scanning until the person taps done, timeoutSeconds passes or the page stops it. At most 1000.
filterDuplicates?booleanReport each symbology and value once per scan. On by default.
timeoutSeconds?number | nullClose the scanner after this long without reading anything; each code restarts the clock. Null waits as long as it takes. At most an hour.
scanWindow?BarcodeRegion | nullOnly read codes inside this region, which the scanner also shows as an aiming guide.
torchOn?booleanStart with the torch lit, where there is one.
title?string | nullShown at the top of the scanner. At most 256 characters.
instructions?string | nullShown under the title. At most 256 characters.
BarcodeFormatenum#

A barcode symbology.

"QrCode""Aztec""DataMatrix""Pdf417""Code128""Code39""Code93""Codabar""Ean8""Ean13""UpcA""UpcE""Itf""Unknown"
BarcodeScanModeenum#

How the scanner reads a frame.

"Standard""LongRange"
BarcodeRegionobject#

A rectangle in normalized (0–1) coordinates of the upright camera image.

xnumber
ynumber
widthnumber
heightnumber
ScannedBarcodeobject#
valuestringThe decoded payload.
formatBarcodeFormatThe symbology.
boundsBarcodeRegionWhere it was in the upright camera image.
BarcodeScanEndReasonenum#

Why a scan ended.

"MaxResults""Done""Dismissed""Timeout""Stopped""PermissionDenied""CameraUnavailable"