---
title: Embedded Barcode Scanner with Camera Preview in Capacitor
description: Render a native camera preview inside your Capacitor app layout for barcode scanning. No transparent web view hacks, no invisible camera preview.
date:
  created: 2026-09-02
  updated: 2026-09-02
authors:
  - robingenz
categories:
  - Capacitor
  - Guides
  - SDKs
links:
  - Capacitor Barcode Scanner: sdks/capacitor/barcode-scanner.md
faq: true
---

# Embedded Barcode Scanner with Camera Preview in Capacitor

Search any Capacitor forum for barcode scanning and one bug dominates the results: the camera preview is invisible. The classic approach renders the camera behind the web view and asks you to make your entire app transparent, which works right up until an Ionic modal or a page transition paints a background over it. The [Capacitor Barcode Scanner plugin](../../sdks/capacitor/barcode-scanner.md) takes a different route: an embedded barcode scanner whose camera preview is a native view positioned inside your app layout, exactly where your markup reserves space for it. This guide shows how to set it up, keep it in sync with your layout, and when to place it behind the web view deliberately. The plugin is available to Capawesome [Insiders](../../insiders/index.md).

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Build and deploy your Capacitor app with Capawesome Cloud" src="https://capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
  </a>
</div>

**Key takeaways:**

- The embedded mode of `@capawesome-team/capacitor-barcode-scanner` renders the camera preview natively inside a frame you measure from your own layout, via `startScan({ frame })`.
- By default the preview sits **above** the web view, so HTML can never accidentally cover the camera. That eliminates the "invisible camera preview" issue class of transparent-web-view approaches.
- Overlaying HTML (viewfinders, detection markers) is an explicit opt-in with `placement: PreviewPlacement.Behind`.
- Detected barcodes stream in through the `barcodesScanned` event, with a configurable `duplicateTimeout` (default 1500 ms) and an optional `detectionArea`.
- The embedded mode also works on the web via the `BarcodeDetector` API; the ready-made fullscreen scanner is native-only.
- Requires Capacitor 8 or later; part of the Capawesome Insiders subscription.

## Why Is the Camera Preview Invisible in a Capacitor Barcode Scanner?

Because in most Capacitor barcode scanning setups, the camera preview is rendered **behind** the web view, and every element in your app that paints a background covers it. The approach relies on making the app transparent during a scan: the plugin hides the web view background, and you add CSS so that the body and every ancestor of your scan area let the camera shine through.

That contract is fragile in exactly the places a real app is dynamic. An Ionic modal brings its own backdrop. A page transition briefly stacks two pages with opaque backgrounds. A toast, a loading overlay, or a dark theme's default background is enough to black out the camera. None of these are bugs in the scanning plugin or in your code; they are the transparency requirement colliding with UI components that were never designed around it. The result is the long tail of "camera not showing" reports that every transparent-web-view scanner accumulates.

To be clear, this is a property of the approach, not of one library. The [Capacitor ML Kit Barcode Scanning plugin](../../sdks/capacitor/mlkit/barcode-scanning.md), which uses this technique for its scan sessions, is actively maintained and works well when you control the transparency carefully. The embedded mode described in this guide removes the requirement instead of managing it.

## An Embedded Camera Preview Inside Your App Layout

The embedded mode of the [Capacitor Barcode Scanner plugin](../../sdks/capacitor/barcode-scanner.md) inverts the layering. [`startScan(...)`](../../sdks/capacitor/barcode-scanner.md#startscan) renders the camera preview as a native view **above** the web view, inside a frame you define in CSS pixels. Your markup keeps a placeholder element where the camera should appear, and the element's bounding rectangle becomes the frame:

```typescript
import {
  BarcodeScanner,
  LensFacing,
} from '@capawesome-team/capacitor-barcode-scanner';

const getScanFrame = () => {
  const rect = document.querySelector('#scanner').getBoundingClientRect();
  return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
};

const startEmbeddedScan = async () => {
  await BarcodeScanner.addListener('barcodesScanned', (event) => {
    console.log('Scanned barcodes:', event.barcodes);
  });
  await BarcodeScanner.startScan({
    frame: getScanFrame(),
    lensFacing: LensFacing.Back,
  });
};

const stopEmbeddedScan = async () => {
  await BarcodeScanner.stopScan();
  await BarcodeScanner.removeAllListeners();
};
```

Because the preview sits above the web view, no background anywhere in your app can hide it. A modal opening mid-scan, a theme change, a CSS refactor months later: the camera stays visible. The flip side is just as deliberate: HTML elements cannot overlap the preview by default, so buttons and labels belong outside the frame. If your design needs overlays inside the camera area, that is an explicit opt-in covered below.

Before starting a session, check [`isAvailable()`](../../sdks/capacitor/barcode-scanner.md#isavailable) and request the camera permission with [`requestPermissions()`](../../sdks/capacitor/barcode-scanner.md#requestpermissions); [`openSettings()`](../../sdks/capacitor/barcode-scanner.md#opensettings) helps users recover from a permanently denied permission.

## Scan Continuously Without Duplicate Events

An embedded session is continuous: detected barcodes are emitted through the [`barcodesScanned`](../../sdks/capacitor/barcode-scanner.md#addlistenerbarcodesscanned-) event until you call [`stopScan()`](../../sdks/capacitor/barcode-scanner.md#stopscan). Three options keep that stream manageable:

- **`duplicateTimeout`** controls when the same barcode is emitted again, with a default of 1500 milliseconds. A parcel label held into the camera does not flood your handler.
- **`formats`** restricts detection to the formats you expect, which also improves performance. The plugin supports 13 formats, from QR code and EAN-13 to Code 128, ITF, and PDF417.
- **`detectionArea`** limits detection to a region within the frame, so only the barcode inside your viewfinder graphic counts, even though the camera sees more.

Each detected barcode arrives with its `rawValue` (the value as encoded), a human-readable `displayValue`, the `format`, and `cornerPoints` for overlays. Two platform details are documented rather than hidden: the raw `bytes` are Android-only (`null` on iOS and the web, whose frameworks expose only the string value), and Apple's frameworks report UPC-A barcodes as EAN-13 with a leading zero, which the plugin normalizes back to `UPC_A` so the behavior stays consistent across platforms. Camera scanning of Codabar barcodes additionally requires iOS 15.4 or later.

Errors during a session, such as the camera becoming unavailable, arrive via the [`scanError`](../../sdks/capacitor/barcode-scanner.md#addlistenerscanerror-) event. And when your app needs a break, for example while processing a hit, [`pauseScan()`](../../sdks/capacitor/barcode-scanner.md#pausescan) suspends detection while the preview keeps running, and [`resumeScan()`](../../sdks/capacitor/barcode-scanner.md#resumescan) picks it back up.

## Keep the Frame in Sync With Your Layout

The native view does not reflow with your CSS, so when the layout changes, tell the plugin. [`setScanFrame(...)`](../../sdks/capacitor/barcode-scanner.md#setscanframe) updates the frame of the active session, which makes orientation changes a one-liner:

```typescript
import { BarcodeScanner } from '@capawesome-team/capacitor-barcode-scanner';

window.addEventListener('resize', async () => {
  await BarcodeScanner.setScanFrame({ frame: getScanFrame() });
});
```

The same call covers collapsing headers, keyboard appearance, or any other change that moves your placeholder element.

## When You Want HTML Overlays After All

Some designs call for a viewfinder drawn in HTML or detection markers rendered over the live camera. For those, set `placement` to `PreviewPlacement.Behind` and the preview renders behind the web view, so HTML can overlap it:

```typescript
import {
  BarcodeScanner,
  PreviewPlacement,
} from '@capawesome-team/capacitor-barcode-scanner';

const startEmbeddedScanBehindWebView = async () => {
  await BarcodeScanner.startScan({
    frame: getScanFrame(),
    placement: PreviewPlacement.Behind,
  });
};
```

This mode has the same transparency requirement as the classic approach: the placeholder element, all of its ancestors, and the `body` must have a transparent background over the frame area. With Ionic components, also set `--background: transparent` on the surrounding `ion-content`:

```css
body,
#scanner {
  background: transparent;
}
```

The difference from the old world is that transparency is now a scoped, deliberate choice for one screen you design around it, instead of a global precondition for scanning at all. For overlays like detection markers, the `cornerPoints` of each barcode are reported in CSS pixels relative to the frame, so mapping them onto absolutely positioned HTML is straightforward.

## Torch, Zoom, and Camera Selection

Warehouse shelves are dark and barcodes are small, so an embedded session exposes the camera controls you would expect. [`setTorchEnabled(...)`](../../sdks/capacitor/barcode-scanner.md#settorchenabled) toggles the flashlight, and [`setZoomRatio(...)`](../../sdks/capacitor/barcode-scanner.md#setzoomratio) sets the zoom within the range reported by [`getZoomRatioRange()`](../../sdks/capacitor/barcode-scanner.md#getzoomratiorange):

```typescript
import { BarcodeScanner } from '@capawesome-team/capacitor-barcode-scanner';

const zoomIn = async () => {
  const { max } = await BarcodeScanner.getZoomRatioRange();
  await BarcodeScanner.setZoomRatio({ ratio: Math.min(2, max) });
};
```

[`getAvailableCameras()`](../../sdks/capacitor/barcode-scanner.md#getavailablecameras) reports the lens facings of the device, so you can hide a camera flip button on devices that only have one camera instead of letting it fail.

## When the Fullscreen Scanner Is the Better Fit

Not every screen needs an embedded preview. For a quick "scan one code and move on" interaction, the plugin also ships a themeable fullscreen scanner via [`scan(...)`](../../sdks/capacitor/barcode-scanner.md#scan), a complete native UI with viewfinder, torch button, and optional flip camera button, in which only barcodes fully inside the viewfinder are detected:

```typescript
import {
  BarcodeFormat,
  BarcodeScanner,
} from '@capawesome-team/capacitor-barcode-scanner';

const scanSingleBarcode = async () => {
  const { barcodes } = await BarcodeScanner.scan({
    formats: [BarcodeFormat.QrCode, BarcodeFormat.Ean13],
    ui: {
      accentColor: '#59C7F9',
      instructions: 'Point your camera at a barcode.',
      title: 'Scan Barcode',
    },
  });
  return barcodes[0];
};
```

The `ui` options cover the accent color (applied to the viewfinder corners and the done button), title, instructions, an optional beep, haptic feedback (on by default), and the torch and flip camera buttons. In single-shot mode the promise resolves with the first detected barcode and rejects with `SCAN_CANCELED` when the user closes the scanner; setting `batch: true` collects multiple barcodes in one session instead. As a rule of thumb: reach for `scan(...)` for occasional one-off scans like a payment QR code or a ticket at the entrance, and for the embedded mode when scanning is part of the screen itself, like continuous parcel-label scanning in logistics or price lookup in retail.

## Does the Embedded Scanner Work on the Web?

Yes. The embedded mode and [`readBarcodesFromImage(...)`](../../sdks/capacitor/barcode-scanner.md#readbarcodesfromimage) are supported on the web through the [`BarcodeDetector`](https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector){:target="_blank"} API. Since not every browser ships it, the plugin documentation recommends the [barcode-detector](https://www.npmjs.com/package/barcode-detector){:target="_blank"} polyfill, which is intentionally not bundled so you stay in control of your bundle size. The ready-made fullscreen scanner ([`scan(...)`](../../sdks/capacitor/barcode-scanner.md#scan)), torch, and zoom are native-only, so the embedded mode is also the way to build a web scanner UI. One more web caveat: `checkPermissions()` is best-effort there, since some browsers cannot query the camera permission and always report `prompt`.

## Coming From the ML Kit Barcode Scanning Plugin?

If your app already scans with the [Capacitor ML Kit Barcode Scanning plugin](../../sdks/capacitor/mlkit/barcode-scanning.md), the embedded mode maps closely to what you know, with the layering as the main behavioral change:

| `@capacitor-mlkit/barcode-scanning`  | `@capawesome-team/capacitor-barcode-scanner`      |
| ------------------------------------ | ------------------------------------------------- |
| `startScan()` (transparent web view) | `startScan()` (embedded native view with `frame`) |
| `hideBackground()` + transparent CSS | Not needed in the default placement               |
| `addListener('barcodesScanned')`     | `addListener('barcodesScanned')`                  |
| `enableTorch()` / `disableTorch()`   | `setTorchEnabled({ enabled })`                    |

Two details to check during a migration: `cornerPoints` are relative to the scan frame rather than the screen, and structured payload parsing is not yet available, so parse `rawValue` yourself where you relied on it. The [full migration table](../../sdks/capacitor/barcode-scanner.md#migration-from-ml-kit-barcode-scanning) is part of the plugin documentation.

## FAQ

### Why is the camera preview invisible in my Capacitor barcode scanner?

Because the camera is rendered behind the web view and something in your app is painting a background over it, commonly an Ionic modal backdrop, a page transition, or a theme background. Either remove every background over the scan area, or use the embedded mode of the [Capacitor Barcode Scanner plugin](../../sdks/capacitor/barcode-scanner.md), which renders the camera preview above the web view so HTML cannot cover it.

### Why can't I place HTML elements over the embedded camera view?

In the default placement, the preview is a native view above the web view, which is exactly what makes it impossible to hide by accident. Put controls outside the frame, use the themeable fullscreen scanner, or opt into `PreviewPlacement.Behind` for overlays.

### Does barcode scanning work offline?

Yes. On Android the plugin uses the bundled ML Kit model, so no Google Play services module download is involved, and on iOS it builds on the system's AVFoundation and Vision frameworks. No network connection is required.

### Is the fullscreen scanner available on the web?

No. On the web, build your scanner UI on the embedded mode, which is fully supported via the `BarcodeDetector` API. Torch and zoom control are also native-only.

### Is the Capacitor Barcode Scanner plugin free?

No. It's part of the Capawesome [Insiders](../../insiders/index.md) subscription, which also covers all other Insiders plugins and priority support.

### Does it work with Ionic, Angular, React, or Vue?

Yes. The plugin is framework-agnostic and works in any Capacitor app, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects.

Once your scanner screen works, the rest of the pipeline still has to ship. Capawesome Cloud covers native builds, live updates, and app store publishing for Capacitor apps.

[Try Capawesome Cloud Free](https://capawesome.io){ .md-button .md-button--primary }

## Conclusion

The invisible camera preview was never a CSS puzzle to be solved harder; it was a layering model working against real-world UI. Positioning the camera as a native view inside your layout removes the failure mode outright, and keeps transparency available as a scoped choice for the screens that genuinely want overlays. The [Capacitor Barcode Scanner plugin](../../sdks/capacitor/barcode-scanner.md) documentation covers everything this guide skipped, including the themeable fullscreen scanner, batch mode, and the [API reference](../../sdks/capacitor/barcode-scanner.md#api).

If you have any questions, join us on the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"}. To stay updated on the latest news, subscribe to the [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"}.
