Laravel Feedback — Floating widget + Filament triage + notification drivers
A drop-in floating feedback widget for Laravel apps — sentiment + free-text + optional email, with a per-page mode switch (feedback / bug / notify), a Filament admin resource for triage, and pluggable notification drivers (mail / Slack / Telegram / webhook). Designed for solo founders and product teams that want voice-of-customer in every property without paying for Userback / Canny / Marker.
Why this exists. "Add a feedback button" is one of those tiny tasks that turns into a week — a widget, an endpoint, rate-limiting, storage, an admin view, notifications, i18n, RTL. This package gives you all of it in one
composer require. It's already running in production in Toolenza — this package extracts it so every Laravel app can use the same surface.
Try it live: Working integrations across
plugins-demo.codenzia.com,aqarkom.com, and Toolenza show the three modes side-by-side.
Features
- Floating Alpine.js widget — bottom-right pill, animated panel, dark-mode aware, RTL-aware (
dir="auto"). - Three built-in modes per page:
feedback— sentiment + message + optional emailbug— same shape, tagged for the bug triage filternotify— email required, message optional, no sentiment — perfect for "Notify me when this ships" lead capture
- Per-page context tagging — pass
['plugin' => 'filament-comments', 'tenant' => 42]and it's captured in thecontextJSON column for downstream triage. - Filament v4/v5 admin resource — list, sentiment column, mode badge, mailto reply action, one-click triage, navigation badge for untriaged count.
- Rate-limited — per-IP, 10/hour by default (configurable). IP is hashed, never stored raw.
- Auto-fills email for signed-in users in non-notify modes.
- Pluggable notification drivers —
mail,slack,telegram,webhook. Drivers run sequentially on every submission via theFeedbackReceivedevent. Extend with your own. - i18n — ships with English + Arabic; publishable for any locale.
- Anonymous-friendly —
user_idis nullable; works for logged-out marketing pages. - Suppressible in embeds — set
View::share('feedbackSuppressed', true)inside iframe routes so embeds don't pollute the host page.
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.3 |
| Laravel | ^11.0 || ^12.0 || ^13.0 |
| Filament (optional) | ^4.0 || ^5.0 — only required for the admin resource |
| Guzzle (optional) | needed by Slack / Telegram / webhook drivers (Laravel already includes it via illuminate/http) |
Installation
Publish the config, migration, lang, and (optionally) views:
Quick Start
1. Drop the widget into your layout
That's it — a "Feedback" pill renders bottom-right and POSTs to /api/feedback.
2. Use the modes per page
3. Register the Filament admin resource
In your panel provider:
Or set feedback.filament.auto_register => true in config to register it on every panel automatically.
Notification drivers
Configure which drivers run on each submission via the FEEDBACK_DRIVERS env var (comma-separated):
Drivers run sequentially and swallow their own exceptions — a misconfigured Slack webhook won't block the mail behind it.
Custom drivers
Add it to FEEDBACK_DRIVERS=mail,intercom and you're done.
Custom event listeners
Hook the FeedbackReceived event for app-specific side-effects:
Database schema
The package's migration creates a feedback_messages table:
| Column | Type | Notes |
|---|---|---|
id |
BIGINT | PK |
user_id |
BIGINT, nullable | FK to your User model (configurable) |
email |
VARCHAR(191), nullable | Anonymous submitters / fallback |
source_url |
VARCHAR(2048), nullable | Page the feedback came from |
mode |
VARCHAR(16) | feedback / bug / notify / custom |
sentiment |
VARCHAR(16), nullable | happy / neutral / sad |
message |
TEXT, nullable | Required in feedback/bug mode, optional in notify |
context |
JSON, nullable | Per-page metadata attached by the host page |
user_agent |
VARCHAR(512), nullable | Truncated |
ip_hash |
VARCHAR(64), nullable | SHA-256(ip + APP_KEY); never raw IP |
created_at |
TIMESTAMP | Defaults to now() |
triaged_at |
TIMESTAMP, nullable | Set by the admin "Triage" action |
Configuration
After publishing, edit config/feedback.php. Key sections:
| Key | Default | Description |
|---|---|---|
route.path |
api/feedback |
The POST endpoint |
rate_limit.max_attempts |
10 |
Submissions per IP per hour; 0 to disable |
validation.message_min |
5 |
Min chars for the message body |
widget.default_mode |
feedback |
feedback / bug / notify |
widget.privacy_url |
/privacy |
Link target for the privacy notice; null to hide |
widget.suppress_in_embeds |
true |
Hide widget when $feedbackSuppressed is shared |
filament.auto_register |
false |
Register the resource on every panel automatically |
notifications.drivers |
mail |
Comma-separated list from env |
Full options in config/feedback.php.
Migrating Toolenza to this package
If you're migrating from a pre-existing in-app implementation (Toolenza's, or anything modelled on it):
- Install the package:
composer require codenzia/laravel-feedback. - Publish only config for now:
php artisan vendor:publish --tag=feedback-config. - Update your existing
feedback_messagesmigration to add the two new columns (or roll a new migration):Schema::table('feedback_messages', function (Blueprint $table): void { $table->string('mode', 16)->default('feedback')->after('source_url'); $table->json('context')->nullable()->after('message'); $table->index('mode'); }); - Delete your in-app
App\Models\FeedbackMessage,App\Http\Controllers\FeedbackController,App\Filament\...\FeedbackMessageResource, and thefeedback-widget.blade.phpcomponent. - Remove the
POST /api/feedbackroute fromroutes/web.php(the package registers it). - Replace every
<x-feedback-widget />(or whatever your in-app name was) with@include('laravel-feedback::widget'). - Register the Filament plugin in your panel provider (see Quick Start above).
- Move any in-app references to
App\Models\FeedbackMessagetoCodenzia\LaravelFeedback\Models\FeedbackMessage. - Run tests. Your existing rows continue to work —
modedefaults tofeedbackfor legacy rows.