mmuqiitf/filament-qr-code
Filament QR Code provides a simple QR code field and display component for Filament forms and tables, letting you generate scannable codes from URLs, text, or model data with minimal setup in Laravel admin panels.
Architecture fit. Still a Filament form-field plugin (Livewire + Alpine/JS scanner) with a server-validated scan pipeline, and v0.6.0 extends that shape rather than changing it. The architectural additions are: (1) runtime control — scanner/sequence options are re-synced from the server on every Livewire re-render (scan.sync_options, default 'morph'), with an explicit OptionsSync::Refresh mode that emits qr-scanner-refresh / qr-sequence-refresh; (2) a public client API — element.qrScanner / element.qrScanSequence (feed, pause, resume, reset, setOptions, refresh, getOptions, getState, goToStep) plus field-scoped window events; (3) server-side extension points — lookupUsing(), normalizeWith()/normalizeUsing(), and ->pipe(Stage::class) implementing a ScanCheckStage contract that runs after built-in checks and also on submit. Net effect: the package is now a small platform with a stable-ish extension surface rather than a single field. That is a better fit for real ops apps (custom input hardware, POS pause/resume, multi-step sequences), but it moves the integration boundary: the package's own contract is the thing you now build against, and pre-1.0 churn in that contract is your upgrade risk.
Integration feasibility. High, but this upgrade is not a drop-in like 0.4.0. Required steps: composer update, php artisan filament:assets, republish customised views (views are now split into publishable sub-views; the scanner no longer embeds options in x-data — they moved to data-qr-options, and the sequence view gained a data-qr-options wrapper). Code-level breaks in the 0.5→0.6 window: ScanResult::$record is now mixed (lookups may return arrays/objects) — replace $record->getKey() with ScanResult::recordKey(); the filament-qr-code.image route name is deprecated in favour of filamentQrCode.image and removed in 1.0; normalizeUsing() now also runs server-side before rules and lookup (not only on stored state), which can change which scans pass; QrScanSequence::make() now runs setUp()/configureUsing(), and sequences with a state path now fill to []. Sequences also change behavior: an optional step whose check fails now yields to a following repeat step. Note the assessment gap — this record was last set at v0.4.0 and the notes are written as "upgrading from 0.5," so the 0.5 deltas were never reviewed here; treat the 0.4→0.6 path as a two-hop upgrade and read both changelogs.
Technical risk. Medium, shifted from pure runtime risk toward upgrade and extension-surface risk. Additions: 'morph' option sync means every Livewire re-render pushes options into a live scanner — good for consistency, but it creates a class of bug where form state, field config, or closures evaluated during render change scanner behavior mid-workflow (and the "in-progress scan finishes first" guarantee only covers one scan, not a sequence step transition). ->pipe(Stage::class) is container-resolved and runs on submit too, so a stage that hits the DB, does network I/O, or throws becomes a per-scan and per-submit cost/failure point with no documented ordering guarantee beyond "after built-ins." normalizeWith(stripPrefix, uppercase, trim) widens the accepted input surface and now runs server-side — a mistuned stripPrefix can collapse distinct codes into one. lookupUsing() returning arbitrary arrays/objects pushes key semantics onto recordKey(); any downstream code assuming an Eloquent model is now a latent type error. Window events aimed at a field (qr-scanner-feed, qr-sequence-set-repeat, …) plus window.FilamentQrCode.router being public increase the JS blast radius: other scripts on the page can drive scanners. Options landing in data-qr-options put configuration into the DOM. Event payloads grew (statePath, livewireClass, livewireId, sequence, values; QrCodeScanned gains state_path) — the audit PII surface is now larger, especially the values array on sequence events, which can carry multiple scanned identifiers. Unchanged risks remain: per-scan Livewire latency pausing the camera, cooldowns suppressing legitimate rescans, unscoped lookup() leaking cross-tenant record existence, stale assets after deploy.
Key questions.
1.0 will remove the filament-qr-code.image route name?fi-qr-* classes, data-qr-part markers, --fi-qr-* CSS variables)?scan.sync_options left at 'morph' or set to Refresh? What is the failure mode if a render-time closure is slow or throws while morphing options into an active scanner?ScanCheckStage ordering, idempotency, timeout, and exception handling — and are stages re-run on submit in the same tenancy/policy context?recordKey() behave correctly for our lookupUsing() return shapes (arrays, DTOs, nulls), and is every consumer of ScanResult::$record audited?data-qr-options in the rendered HTML — do we leak routes, IDs, or lookup configuration to anyone who can view source?normalizeWith(stripPrefix:), and can two legitimate distinct codes normalize to the same value?values array on ScanSequenceCompleted / ScanRejected, and does that change our PII retention posture versus 0.4?make:qr-scan-field (and published stubs) become our standard scaffolding, and do we standardize on the tested docs/recipes patterns (pos, warehouse, manufacturing) rather than bespoke wiring?handleScan() budget still met after adding stages and lookupUsing() custom queries, and what is the camera/modal behavior on Livewire timeout?Stack fit. Unchanged dependency posture: Filament + Livewire + Alpine, no new runtime services. The integration surface is richer but still idiomatic: normalizeWith() presets and normalizeUsing() as server-side preprocessing, lookupUsing() for non-Eloquent resolution, ->pipe(Stage::class) for custom checks bound through the container, ScanStep::when() for conditional steps, and element-level JS control (pause/resume/feed/goToStep) for hardware-driven flows. The docs/recipes examples are linted and API-checked by tests/Feature/RecipesTest.php, which makes them the safest starting point — prefer adapting a recipe over writing wiring from scratch. php artisan make:qr-scan-field Name [--sequence] gives a supported scaffold; publish stubs via --tag=filament-qr-code-stubs only if you need house style.
Migration path. Two-hop and operationally specific. From 0.4: upgrade to 0.5, validate, then to 0.6 — or pin directly to 0.6 and work the combined break list: (1) composer update; (2) php artisan filament:assets; (3) republish customised views (required — x-data no longer carries options, sequence view wraps in data-qr-options); (4) replace $record->getKey() with ScanResult::recordKey(); (5) migrate filament-qr-code.image → filamentQrCode.image now, since 1.0 removes the alias; (6) re-verify any normalizeUsing() behavior, since it now affects server-checked scans and stored state; (7) re-test sequences where an optional step's failure previously blocked — it may now advance to a repeat step. Clear Filament/Livewire caches and confirm asset cache-busting in the pipeline. Rollback to 0.4/0.3 requires republishing the old views and assets, so keep the previous published view set and asset manifest under version control; data written by new event payloads (values, state_path) is forward-only.
Compatibility. Verify Filament/Livewire constraints and the PHP version floor before upgrading. Treat ScanCheckStage implementations and lookupUsing() closures as application code: unit-test them under real tenancy and policy context, and confirm they behave identically on the submit path. Check every consumer of ScanResult::$record for model assumptions now that it is mixed. Review data-qr-options output on a rendered page and decide whether any option values are sensitive. Publish and review config (scan.sync_options, scan.cooldown_ms, scan.clear_on_reject, feedback config) rather than accepting defaults, and adopt the --fi-qr-* CSS variables rather than overriding compiled styles so future view changes don't break you.
Sequencing.
recordKey(), route name, view republish) and confirm no deprecation warnings in logs for the image route.normalizeUsing() semantics and any stripPrefix/uppercase presets against real code populations — confirm no collision on distinct codes.docs/recipes patterns field-by-field; replace bespoke JS wiring with the documented element API / window events and remove ad-hoc wire:key re-mounts.lookupUsing() and one ScanCheckStage on a low-risk field; measure added latency on submit as well as on scan.scan.sync_options ('morph' vs Refresh) per field; test mid-scan re-renders, option changes, and multi-step transitions.statePath, livewireClass, livewireId, sequence, values) and re-confirm the PII/retention decision.cooldown(), clearOnReject(), per-field feedback, and pause/resume UX on real handhelds; then make ScanChecksRule the submit-time backstop everywhere.Maintenance. Higher than at 0.4. The package is pre-1.0 with an accelerating surface: this release spans runtime control, extension points, and a docs/recipes programme, and it explicitly schedules a removal (filament-qr-code.image in 1.0). Budget recurring work for: asset republishes, published-view drift reconciliation (views are now split sub-views — every upstream view change is a potential merge), ScanCheckStage and lookupUsing() code ownership, normalizer tuning, option-sync mode decisions, and event-consumer updates as payloads grow. Pin exact versions, read UPGRADE.md for every release, and treat each minor bump as a mini-project rather than a patch.
Support. Support load shifts further from "scanner doesn't open" toward "scan was rejected," "camera stayed paused," "sequence skipped a step," and "the wrong step is required." New diagnosability helps — statePath/livewireId/sequence/values on ScanAccepted/ScanRejected, onStepAccepted()/onStepRejected() hooks, getState() on the element — but only if support tooling can query events by field and step. Define a rejection-reason playbook covering record_not_found, scan_failed, and stage-originated failures, plus a recovery procedure for a stalled check. Because pages can now drive scanners via window events, "something else on the page moved my scanner" becomes a support category; document which scripts are allowed to emit them.
Scaling. Unchanged shape: one Livewire request per live scan, so scan volume is web-tier load, and bursty shifts need p95 headroom, not average. Two new cost centers: (1) pipe(Stage::class) stages run per scan and per submit — enforce a no-I/O-by-default rule and cap stage count per field; (2) lookupUsing() custom queries bypass the indexed lookup() path, so their query plans need the same index review. 'morph' option sync adds work to every Livewire re-render, which matters most on pages with many fields or frequent updates. Duplicate suppression remains partly client-side — do not treat it as rate limiting; add server-side throttling on handleScan() for broad-audience fields.
Failure modes. Livewire timeout or 5xx mid-check still risks a paused camera and an uncertain operator, now compounded by sequence state (k / n, active step, repeat count) which follows the server — a failed request can leave the browser display disagreeing with server state until next render; verify refresh/reset recovery. Mid-scan option morphing can change step requirements under the operator; "the scan in progress finishes first" does not cover the next step. Mistuned stripPrefix/uppercase normalizers silently accept the wrong code, and since normalization now runs server-side the error is invisible to the client. A throwing or slow ScanCheckStage fails both live scans and submit. Republished-view drift after an upstream release causes markup/CSS mismatches (fi-qr-*, data-qr-part) that look like styling bugs. Stale assets after deploy remain the classic JS/server contract mismatch. data-qr-options in the DOM and page-scoped window control widen the information-disclosure and tampering surface. Expanded event values arrays can persist multiple identifiers per scan, growing the audit store faster than at 0.4. Unscoped lookup()/lookupUsing() still risks cross-tenant validation or existence disclosure. Finally, 1.0 will remove the filament-qr-code.image route name — any lingering reference is a scheduled outage.
Ramp-up. For a Filament-proficient developer: low-to-moderate to upgrade and keep existing fields working (a day for composer/assets/views plus a day for the recordKey()/route/normalizer fixes), then several days to adopt recipes, lookupUsing(), a stage or two, and option-sync tuning on representative devices. The linted recipes and make:qr-scan-field measurably lower the floor for new fields — standardize on them. For operations/support: moderate and growing, driven by sequence steps and page-driven scanner control; they need a rejection-reason playbook, a sequence-step troubleshooting guide, a stalled-scanner recovery script, and event visibility keyed by statePath. Budget real-device testing on the actual handhelds for pause/resume, feed, vibration/flash timing, and sequence transitions — desktop browsers will not surface any of it, and neither will they surface a mistuned normalizer accepting bad codes.
How can I help you explore Laravel packages today?