Skip to main content
BarcodeScannerField
Added in v2.6.0
← Back to Table of Contents

Summary

Barcode and QR input on the FlexTextInput shell with a Filament modal camera scanner, optional manual typing, format whitelist, and EAN/UPC checksum validation. Uses a hybrid scan engine: native BarcodeDetector when the browser supports it (Chrome/Edge), with automatic ZXing fallback. On a successful scan the field fills the input, plays a confirmation sound, and closes the modal (unless continuous() is enabled).
ClassBjanczak\FilamentFlexFields\Filament\Forms\Components\BarcodeScannerField
State typestring|null — scanned or typed barcode value (symbology is not stored)
Model cast'sku' => 'string' (typical)
FieldType(no dedicated FieldType mapping yet — use the class directly)
Playgroundbarcode-scanner-field slug in Flex Fields playground

Basic usage

use Bjanczak\FilamentFlexFields\Filament\Forms\Components\BarcodeScannerField;

BarcodeScannerField::make('sku')
    ->label('Product barcode')
    ->placeholder('Scan or type barcode…')
    ->required();
Filament resource example:
use Filament\Forms\Form;
use Bjanczak\FilamentFlexFields\Filament\Forms\Components\BarcodeScannerField;
use Bjanczak\FilamentFlexFields\Enums\BarcodeFormat;

public static function form(Form $form): Form
{
    return $form->schema([
        BarcodeScannerField::make('ean')
            ->label('EAN barcode')
            ->formats([BarcodeFormat::Ean13, BarcodeFormat::Ean8])
            ->validateChecksum()
            ->allowManualInput(false)
            ->helperText('Scan the product barcode with your camera.')
            ->columnSpanFull(),
    ]);
}

State format

The field stores a plain string (or null when empty). Whitespace is trimmed on hydrate/dehydrate.
// Valid EAN-13 example
'5901234123457'

// QR / Code128 example
'SKU-WAREHOUSE-42'
The detected symbology is not persisted in state. Validation uses heuristics on the string (see Format detection).

Supported formats

Configure allowed symbologies with formats() or supportedFormats(). When omitted, all built-in formats are allowed.
Enum caseSlugTypical useExample test value
BarcodeFormat::QrqrQR codes, tickets, URLshttps://example.com/t/abc
BarcodeFormat::Ean13ean_13Retail products (EU)5901234123457 ✓ checksum
BarcodeFormat::Ean8ean_8Small retail packages96385074
BarcodeFormat::UpcAupc_aUS retail (12 digits)036000291452
BarcodeFormat::UpcEupc_eCompressed UPC042526
BarcodeFormat::Code128code_128Logistics, GS1-128ABC-128-TEST
BarcodeFormat::Code39code_39Industrial, inventoryABC-123
BarcodeFormat::ItfitfCarton / interleaved 2 of 51234567890
BarcodeFormat::Pdf417pdf417IDs, boarding passesPDF417-DEMO-VALUE
BarcodeFormat::DataMatrixdata_matrixSmall parts, pharmaDM-TEST-001
Invalid checksum example (EAN-13): 5901234123450.

Limit formats to specific symbologies

Yes — pass only the formats you need. The whitelist is applied in three places:
  1. Camera engine — BarcodeDetector / ZXing hints (what the camera tries to decode).
  2. Client validation — rejects scans that do not match allowed patterns before accepting.
  3. Server validationBarcodeValidator on form submit.

EAN / UPC only (retail)

BarcodeScannerField::make('ean')
    ->formats([
        BarcodeFormat::Ean13,
        BarcodeFormat::Ean8,
        BarcodeFormat::UpcA,
        BarcodeFormat::UpcE,
    ])
    ->validateChecksum()
    ->allowManualInput(false);

QR only

BarcodeScannerField::make('ticket_code')
    ->formats([BarcodeFormat::Qr])
    ->modalHeading('Scan ticket QR code');

Warehouse — Code 128 + ITF

BarcodeScannerField::make('pallet_id')
    ->formats([BarcodeFormat::Code128, BarcodeFormat::Itf])
    ->scanDelay(500);

Dynamic whitelist (Closure)

BarcodeScannerField::make('code')
    ->formats(fn (): array => $this->record?->requires_qr_only
        ? [BarcodeFormat::Qr]
        : [BarcodeFormat::Ean13, BarcodeFormat::Code128]);

Format detection

There are two layers:
LayerWhat it does
Camera (ZXing / BarcodeDetector)Decodes the physical symbology from the image. Respects formats() hints.
Validation (JS + PHP)Checks whether the string value matches allowed patterns (regex/heuristics).
Important:
  • By default state is a string (value only). Enable storeDetectedFormat() to persist { value, format } from the camera engine.
  • Server-side matching is heuristic (e.g. 13 digits → EAN-13). Ambiguous strings may match the first allowed format in priority order (EAN before QR).
  • BarcodeFormat::Qr is the widest pattern — avoid combining it with numeric formats if you rely on manual typing.
  • validateChecksum() adds real modulo-10 verification for EAN/UPC (recommended for retail).

Scan UX

Default flow (without continuous()):
  1. User clicks scan button → Filament modal opens.
  2. Camera starts after the modal transition (badge shows Native engine on desktop Chrome/Edge or ZXing engine on mobile).
  3. On valid scan → input filled, confirmation beep (when beepOnScan() is on), modal closes immediately.
  4. Toolbar below the previewSwitch camera and Turn torch on/off (when supported); not overlaid on the video.
  5. On mobile, switch camera toggles front ↔ rear via facingMode; on desktop, cycles MediaDeviceInfo when multiple inputs exist.
  6. When pauseWhenHidden() is on (default), decoding pauses while the browser tab is in the background to save battery.
With continuous() — modal stays open for inventory-style scanning; input updates on each successful read.

Mobile / iOS notes

TopicBehaviour
Camera previewBody-mounted video portal + synced overlay so WebKit renders the stream inside the Filament modal
Switch cameraenvironmentuser facing toggle (recommended on phones)
Success soundShort Web Audio tone — no lock-screen / Control Center “Now Playing” entry
Desktop soundBundled MP3 (barcode-scan-success.mp3) with synthesized fallback

Configuration API

All methods accept Closure for dynamic configuration.
MethodDescriptionDefault
formats() / supportedFormats()Allowed symbologies (list<BarcodeFormat|string>)all formats
validateChecksum()Modulo-10 check for EAN/UPCfalse
continuous()Keep modal open after each scanfalse
beepOnScan()Play confirmation sound on success (MP3 on desktop; transient Web Audio on mobile)true
autoSubmit()Dispatch barcode-auto-submit + form.requestSubmit()false
cameraFacing('environment'|'user')Preferred camera when no preferredDeviceId()environment
preferredDeviceId(?string)Pin a specific MediaDeviceInfo.deviceIdnull
allowCameraSwitch()Show switch-camera in toolbar (facingMode on mobile; cycle devices on desktop)true
scanDelay(int)Debounce duplicate reads (ms)750
scanInterval(int)Decode loop interval (ms), clamped 50–2000120
decodeFps(int)Shorthand for scanInterval via 1000 / fps
pauseWhenHidden()Pause decode when tab is hidden (Page Visibility API)true
storeDetectedFormat()State { value, format } instead of plain stringfalse
allowManualInput()Text input fallbacktrue
scanButtonLabel()Scan button / aria-labeltranslated
modalHeading()Filament modal titletranslated
scanIcon()Scan trigger iconQR icon
barcodeRule(Closure|string)Extra Filament validationnone
variant()primary, secondary, soft, flat, ghostprimary
size()sm, md, lgmd

placeholder(string|Closure|null $placeholder)

Inherited from Filament HasPlaceholder. Default translation: filament-flex-fields::default.barcode_scanner.placeholder.
BarcodeScannerField::make('sku')->placeholder('Scan or type EAN…');

readOnly(bool|Closure $condition = true) / disabled(bool|Closure $condition = true)

Inherited from Filament CanBeReadOnly. Read-only keeps the scanned value visible but blocks manual edits and opening the scanner modal.
BarcodeScannerField::make('registered_sku')
    ->default(fn ($record) => $record->sku)
    ->readOnly();

focusOutline(bool|Closure $condition = true)

Inherited from HasFieldFocusOutline. Default: false. When true, shows the shared focus ring on the FlexTextInput shell.
BarcodeScannerField::make('sku')->focusOutline();

Public helper methods

MethodReturnsDescription
getBeepUrl()stringURL for desktop confirmation MP3
getAlpineConfiguration()arrayFull config passed to Alpine (supportedFormats, labels, etc.)
getWrapperClasses()array<string, bool>Root CSS class map
Standard Filament field methods (required(), rule(), unique(), live(), helperText(), …) work as usual.

Model & persistence

// Migration
$table->string('sku')->nullable()->unique();

// Model — plain string, no special cast
protected $fillable = ['sku'];
BarcodeScannerField::make('sku')
    ->default(fn (?Product $record): ?string => $record?->sku)
    ->formats([BarcodeFormat::Ean13])
    ->validateChecksum();
When storeDetectedFormat() is enabled, cast or accessor handling may be needed if you persist the full { value, format } array — the default string column stores only the barcode value.

Recipes

Retail shelf — EAN-13 with checksum, scan-only

BarcodeScannerField::make('barcode')
    ->formats([BarcodeFormat::Ean13])
    ->validateChecksum()
    ->allowManualInput(false)
    ->required();

Inventory loop — continuous + beep

BarcodeScannerField::make('scan_buffer')
    ->continuous()
    ->beepOnScan()
    ->scanDelay(400)
    ->formats([BarcodeFormat::Code128, BarcodeFormat::Code39]);

Auto-submit lookup form

BarcodeScannerField::make('lookup_code')
    ->autoSubmit()
    ->live()
    ->afterStateUpdated(fn (?string $state) => $this->lookupProduct($state));

Laptop with multiple cameras + slower decode

BarcodeScannerField::make('asset_tag')
    ->allowCameraSwitch()
    ->decodeFps(5) // ~200 ms between attempts
    ->pauseWhenHidden();

Persist symbology from camera

BarcodeScannerField::make('scan')
    ->storeDetectedFormat()
    ->formats([BarcodeFormat::Ean13, BarcodeFormat::Qr]);

// State: ['value' => '5901234123457', 'format' => 'ean_13']
// Input still shows value only; use $get('scan.format') or state array in callbacks.

Custom validation + uniqueness

BarcodeScannerField::make('sku')
    ->formats([BarcodeFormat::Code128])
    ->barcodeRule('regex:/^[A-Z0-9-]{6,32}$/')
    ->unique('products', 'sku')
    ->validationMessages([
        'unique' => 'This SKU is already registered.',
    ]);

Events

Alpine dispatches browser events on successful scans:
EventDetail
barcode-scanned{ value, format, engine, statePath }format / engine from camera when available
barcode-auto-submit{ value, statePath } — when autoSubmit() is enabled
Listen in a parent Alpine scope or Livewire hook as needed.

Validation

Built-in rules run through BarcodeValidator:
CheckMessage key
Empty required fieldLaravel validation.required
Value does not match any allowed formatbarcode_scanner.validation.unrecognized
EAN/UPC checksum failurebarcode_scanner.validation.checksum
Custom barcodeRule()your message
Client-side validation mirrors server rules before accepting a camera scan (invalid scans show an inline error in the modal without closing it).

Accessibility

  • Scan button: keyboard operable, aria-haspopup="dialog".
  • Modal: native Filament &lt;x-filament::modal&gt; — focus trap, Escape to close, header close button.
  • Loading state: aria-live="polite" while camera starts.
  • Errors: role="alert" in modal body.
  • @media (prefers-reduced-motion: reduce) disables scan-line animation and spinner.

CSS classes

ClassPurpose
fff-barcode-scannerRoot wrapper
fff-barcode-scanner-fieldField wrapper (via getWrapperClasses)
fff-barcode-scanner__scan-btnScan trigger in input suffix
fff-barcode-scanner__viewportCamera preview shell (portal-synced on mobile)
fff-barcode-scanner__reticleScan frame + animated scan line
fff-barcode-scanner__engine-badgeNative / ZXing engine indicator (compact, top-left)
fff-barcode-scanner__toolbarRow below preview — switch camera & torch
fff-barcode-scanner__switch-camera-btnSwitch camera control
fff-barcode-scanner__torch-btnTorch toggle control
fff-barcode-scanner-modalFilament modal root modifier
Stylesheet: barcode-scanner-field (lazy). Depends on flex-text-input.

Assets

AssetLoading
CSS barcode-scanner-fieldLazy per field
Alpine barcode-scanner-fieldLazy per field
ZXing chunkDynamic import when camera opens
MP3 barcode-scan-success.mp3Published to public/.../audio/...
Rebuild after JS/CSS changes:
cd packages/filament-flex-fields
npm install
npm run build

Playground

Slug: barcode-scanner-field
Demo fieldShows
barcode__defaultDefault + prefilled valid EAN-13
barcode__ean_onlyEAN/UPC whitelist + checksum
barcode__continuousContinuous inventory mode + beep
barcode__checksumEAN-13 only + checksum enforcement
barcode__manual_onlyScan-only (manual input disabled)

Testing

php artisan test --compact packages/filament-flex-fields/tests/Unit/BarcodeScannerFieldTest.php
php artisan test --compact packages/filament-flex-fields/tests/Feature/BarcodeScannerFieldRenderTest.php
node --test packages/filament-flex-fields/tests/js/barcode-scanner-field.test.mjs
Use playground values 5901234123457 (valid EAN-13) and 5901234123450 (invalid checksum) to verify validation.