Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Visual Feedback For Laravel Laravel Package

pushery/visual-feedback-for-laravel

Visual feedback for Laravel apps: surface toasts, alerts, and inline UI cues so users always know what happened. Simple installation, sensible defaults, and Blade-friendly helpers keep feedback consistent without extra frontend work.

View on GitHub
Deep Wiki
Context7

Getting Started

Fix your mental model before writing any code. The listing copy sells "toasts, notifications and progress indicators" and a fluent success()/error() API. What the package actually ships is an in-page feedback widget: a floating trigger, a modal form, client-side screenshot capture with an optional editor, abuse protection, and pluggable delivery (mail / database / webhook). config/visual-feedback.php is documented inline and is the real product spec. If you were expecting flash-message helpers, stop now and read the config before you commit to the integration.

Install and publish. The config publish is mandatory; the asset publish becomes mandatory the moment you leave local, because the bundles are copies, not symlinks.

composer require pushery/visual-feedback-for-laravel
php artisan vendor:publish --tag="visual-feedback-config"
php artisan vendor:publish --tag="visual-feedback-assets"
php artisan vendor:publish --tag="visual-feedback"   # translations + views, optional

The service provider registers itself through package discovery. Note that --tag="visual-feedback" is not everything: config, lang, views and assets each have their own tag, and migrations (visual-feedback-migrations) and the WireKit view tree (visual-feedback-wirekit) sit outside it deliberately, because each is a decision rather than boilerplate.

Wire three things into the layout you already own, in this order.

{{-- resources/views/layouts/app.blade.php --}}
<head>
    @include('visual-feedback::style')
    @livewireStyles
</head>
<body>
    @livewire('visual-feedback.report-widget')
    @livewireScripts
    <x-visual-feedback::scripts />
</body>

That renders the widget. It does not yet deliver anything.

Then do the three things that decide whether it works at all:

  1. Give the mail channel a recipient. VISUAL_FEEDBACK_MAIL_TO ships empty because none could be guessed. Until it is set, the channel marks itself unavailable, is silently skipped, and the reporter is told the submission did not go through. The reasons land in the log only: an enabled channel reported itself unavailable and was skipped, then a report was accepted but no channel was enabled and available to deliver it.
  2. Check MAIL_MAILER. log and array let Mailer::send() succeed with the message going nowhere, and Laravel's own default is env('MAIL_MAILER', 'log') — so an app that never set it is already in that state. The package refuses log, array, and a failover chain that can fall through to one. VISUAL_FEEDBACK_MAIL_MAILER names a real mailer for reports alone when the app itself is deliberately on array.
  3. Run a queue worker. Every channel dispatches a job. A fresh Laravel app defaults queue.default to database, so reports are accepted, acknowledged, and never delivered.

Where to look first: config/visual-feedback.php, then php artisan about — under Visual Feedback it carries a Published bundle line reading up to date, OUT OF DATE (with the command to run), or not published. That one check eliminates the most common self-inflicted failure before you debug anything else.

First use case: mail-only, on a single internal or beta surface, with screenshot.strategy=dom. That exercises the whole loop — trigger, capture, validation, delivery — with the smallest operational tail: no migration, no retention job, no triage UI. Judge it after 30 days on reports per week, share arriving with a usable screenshot, median time-to-reproduce versus your email baseline, delivery failure rate, and whether reports land in triage without a human re-filing them. Only then promote to database or webhook.

Implementation Patterns

Confine the integration to exactly one include. Put the three lines in resources/views/partials/feedback.blade.php and @include that from the layout, so removal, environment opt-out and forking all collapse to one-line operations:

@if (config('services.feedback.enabled'))
    @include('partials.feedback')
@endif

For a young, single-maintainer, low-star package this is cheap insurance, not paranoia.

Treat the channel as a product decision, not a config value. mail means a human reads an inbox. database means reports become queryable records with a built-in browser, pagination and retention. webhook means one queue instead of two. Most teams should start mail and move to webhook; adopt database only if you genuinely want to own a triage UI.

Database channel checklist. The migration sits outside the umbrella tag on purpose:

php artisan vendor:publish --tag="visual-feedback-migrations"
php artisan migrate
# config retention_days drives the prune — schedule it

Enabling VISUAL_FEEDBACK_CHANNEL_DATABASE=true without that publish gives you a channel that accepts a report and a queued job that fails far from the switch that caused it.

Shape the form per page at the call site, not via env churn. Every field answers one question with one word — off, optional or required — defaulting from env and overridable per mount:

<livewire:visual-feedback.report-widget :fields="['email' => 'required', 'subject' => 'off']" />

A value that is none of those three words renders the field as optional, so a typo never removes an input. A field set to off is dropped, not hidden: a posted value for it never reaches the report. message has no mode. Name, email and phone are asked of guests only — an authenticated reporter's identity comes from the auth guard. When guests can submit, set VISUAL_FEEDBACK_PRIVACY_URL so the acknowledgement exists.

Choose the screenshot strategy deliberately, once, with a measurement.

Strategy Behaviour When to pick it
auto Native capture with a one-time permission prompt, DOM fallback Customer-facing surfaces where fidelity matters
native Native capture only You've confirmed reporters grant the prompt
dom No prompt, deterministic, smaller captures Internal tools, dashboards, admin panels — usually right
off Drops the capture bundle and the ~276 KB renderer You only want typed reports

There are four bundles and they fail differently: visual-feedback-widget.iife.js (<12 KB, always ships, registers the Alpine components — missing it means every control does nothing), visual-feedback.iife.js (<40 KB, the capture state machine), visual-feedback-renderer.iife.js (~276 KB, the lazy DOM renderer) and visual-feedback-editor.iife.js (<32 KB, the lazy crop/mark editor). The last two are fetched from a URL derived from the script that loads them, so they must sit beside the capture bundle under the same base. VISUAL_FEEDBACK_SCREENSHOT_EDITOR=false removes the Edit button and the file is never fetched.

Re-publish assets as a deploy step. Because the renderer URL is derived from the loader script, a stale bundle renders a widget whose controls do nothing, with only the console to show for it:

php artisan vendor:publish --tag=visual-feedback-assets --force

If you serve from your own CDN, copy all four together. php artisan about and (with APP_DEBUG=true) a log warning both flag staleness.

Do the CSP edit in the same change as the include. All four scripts must be permitted under the same script-src source. Ship the policy edit alongside the layout edit, or the widget works locally and is invisible in production — the classic first-deploy surprise.

wire:navigate is the first-class path. The widget is a Livewire component and the recent changelog is dominated by SPA-navigation bundle-ordering fixes. Test the first navigation to a widget-carrying page specifically; that's the path that historically produced a spurious "did not load" console message before the second bundle arrived.

Test the wiring, not the delivery. Channels queue, so fake the queue and assert the job:

Queue::fake();
// ... exercise the submit path ...
Queue::assertPushed(SendFeedbackReport::class);

Pair it with a Mail::fake() / Http::fake() integration test for the channel body, and set queue.default=sync in the test env or your assertions race the worker. The deliverable-transport check is never applied while the app runs its tests.

Instrument from day one. Silent failure is the dominant risk class — no worker, no recipient, MAIL_MAILER=log, stale bundles: none of them throw. Alert on "no channel was enabled and available" in the log, and record the capture strategy plus screenshot_edited alongside wherever the report landed, so you can tell "user drew on it" from "the DOM fallback produced a degraded image" without emailing the reporter back.

Gotchas and Tips

  • The umbrella publish tag is not everything. --tag="visual-feedback" covers config, translations, views and bundles. Migrations (visual-feedback-migrations) and the WireKit view tree (visual-feedback-wirekit, config-selected, needs no publishing at all) stand outside it on purpose.
  • Silent failures are the risk class to design against. No queue worker, unset VISUAL_FEEDBACK_MAIL_TO, MAIL_MAILER=log, stale bundles: all four accept the report and drop it quietly. The package refuses log and array for its own channel, but never leave MAIL_MAILER=log on a staging box that accepts feedback — a base64 screenshot in your log stack propagates into every error tracker behind it.
  • Rate limit 0 is not an off switch. Since 0.18.1, VISUAL_FEEDBACK_ABUSE_RATE_LIMIT and VISUAL_FEEDBACK_ABUSE_GUEST_RATE_LIMIT floor to 1 and hold at the shipped 30 and 5 in the config array too. Only the instance-wide global_rate_limit reads 0 as off. Inherited code reading visual-feedback.abuse.rate_limit directly now sees 30/5 where it used to see 0.
  • Refusals happen before storage (0.18.1). Guests on a require_authentication install, spent rate limits, and over-limit file counts or bytes are rejected before anything reaches disk, and refused uploads are dropped from the widget. On S3 that removed a billed PUT+DELETE per file per retry — worth knowing if you audit abuse cost.
  • Screenshots carry whatever was on screen. The package correctly insists on a private attachments disk (VISUAL_FEEDBACK_ATTACHMENTS_DISK, default local), but policy, retention, the DSAR path and redaction are yours — there is no built-in redaction. Set retention_days, schedule the prune, and confirm your backups inherit the same classification.
  • Report browser hardening (0.18.1). Malformed report IDs (non-UUID) and non-Y-m-d from/to bounds are now ignored instead of reaching the database — previously a hard PostgreSQL error, and on MySQL/SQLite a bound that could hide every report. Ordering is created_at, id, so same-second reports no longer jump between pages or vanish.
  • Debug a missing widget in this order: browser console (load errors, CSP blocks) → asset freshness via php artisan about → whether screenshot.strategy=off dropped a bundle you expected → whether the Livewire component is actually mounted on that route.
  • Master switch semantics. VISUAL_FEEDBACK_ENABLED=false refuses every submission before it touches a disk. Useful for a per-environment opt-out behind the single-include pattern.
  • Pin the version and read the changelog on upgrade. Releases are actively fixing production edges — bundle ordering under wire:navigate, PostgreSQL type errors, pagination stability, abuse-path uploads. That is a good sign for a package this young and a reason to upgrade deliberately rather than float a constraint.
  • Maturity signals won't clear a strict dependency gate. Six stars, zero dependents, single maintainer, MIT, and a last release dated in the future relative to typical review windows. Buy the risk down structurally — single include, version pin, changelog review, alerting on the silent-failure log lines — rather than promising yourself you'll notice.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route