yousef-aman/laravel-feedback-board
yousef-aman/laravel-feedback-board provides a ready-to-use feedback and feature-request board for Laravel. Users can submit posts, upvote ideas, and comment, while admins moderate submissions and update statuses. Includes migrations, routes, controllers, and Blade views for quick setup.
Install and scaffold in two commands:
composer require yousefaman/laravel-feedback-board
php artisan feedback-board:install
The install command publishes the config (config/feedback-board.php) and the migrations for reports, comments, attachments and activity. Run php artisan migrate next. Nothing is enabled until you wire two things: a place to launch the form from, and a definition of who counts as a developer.
The first use case is the launcher. Drop the component into whatever layout renders for signed-in users:
{{-- resources/views/layouts/app.blade.php --}}
@auth
<x-feedback-board::launcher />
@endauth
Guests render nothing at all, so it is safe to leave in a layout that also serves a login page — you don't need the @auth wrapper, but keeping it makes intent obvious.
Then decide who sees the board. Out of the box the package checks the feedback-board.manage gate, so the smallest working setup is a policy or a Gate::define in a service provider:
Gate::define('feedback-board.manage', fn (User $user) => $user->isStaff());
First thing to try after that: submit a report as a normal user, then open the board as a developer. Without Filament that is /feedback-board/feedback/board (route name feedback-board.board.page); the reporter's own list is /feedback-board/feedback (feedback-board.page). If you use Filament 4 or 5, register the plugin instead and skip the standalone routes entirely.
Files worth reading in the package before you customise anything: the published config (authorization, auth guards, destinations), the FeedbackBoard facade, and the migration stubs for the report table — the technical context column is what you'll be querying later if you build your own reporting.
Filament panels as the default surface. Register the plugin in each panel provider where you want the board:
->plugin(FeedbackBoardPlugin::make()->myReports(false))
Inside a panel the package uses that panel's guard and primary colour, and its pages become panel pages in the navigation. ->myReports(false) is the usual choice for an internal-staff panel where everyone is a developer and the reporter page would just be noise. Reports are app-wide even on tenanted panels — do not expect tenant scoping.
Resolvers over gates for anything non-trivial. Two hooks do most of the work day to day:
FeedbackBoard::resolveDevelopersUsing(fn (User $u) => $u->hasRole('support'));
FeedbackBoard::resolveReportersUsing(fn (User $u) => $u->hasRole('client'));
Resolvers return booleans (or a gate Response); anything else denies. They run on every launcher render and every gate check, so keep them cheap — a role column, not a query per permission. Cache the result on the model if you must hit the database.
Multiple user populations. The common real-world shape is staff on web and customers on a client panel's own guard. Configure both lists and let the package resolve across them:
'auth' => [
'guards' => ['web', 'clients'],
'developer_guards' => ['web'],
],
FeedbackBoard::user() is the accessor to use in your own code, not auth()->user(), which only sees the default guard.
Destinations as an observability layer. Webhook, Slack and GitHub destinations are configured in the same file and delivered on a queue. The pattern that pays off is pointing the webhook at your existing ops pipeline (a signed endpoint that files into your own tracker) and using GitHub issue creation from the report dialog for the subset of reports that need engineering. Because deliveries are isolated per destination, a broken Slack token does not block GitHub issues.
Ingest for CI and bots. The HMAC-signed ingest endpoint is the clean way to close the loop: a deploy job comments "shipped in v2.3.1" on the report, or an agent runner posts analysis back. It is off by default; turn it on, then send signed JSON with no query string and a JSON content type. Set ingest.nonce_store to a shared cache store the moment you run more than one web node, otherwise signature replay protection is per-server.
AI triage as a triage aid, not a decision. Enable a provider, let the job suggest type and priority, and apply the fields one at a time from the card. Keep ai.queue set to a dedicated queue so a slow provider call never queues behind your transactional mail.
Gate::before() hook can silently disable everything. The launcher and report creation both pass through the feedback-board.report gate, and a before hook that returns false for abilities it doesn't recognise hides the button with no error. If the launcher vanishes after you add a global gate hook, that's the cause.auth.developer_guards is set, nobody outside those guards is a developer regardless of what your resolver returns — the only escape hatch is Gate::before(). Guards whose providers share the same model class cannot be distinguished, so don't try to use two guards over one User model to separate staff from customers.php artisan config:cache, but a closure placed directly in the config file will not survive. Register resolvers in a service provider instead.body: "" on the webhook. If your webhook consumer assumes a non-empty body, it will break on exactly these./command injection. That is a feature — don't try to unescape it in your own integration. destinations.github.reporter_replies turns reporter replies off entirely if the noise outweighs the value.queue:work.FeedbackBoard::linkReportsUsing() once rather than patching notification templates.yousefaman/laravel-feedback-board left the namespace, config keys, routes and tables untouched. If you installed under an earlier name, swap the composer.json requirement; no migration or namespace rewrite is required.Extension points worth knowing before you fork anything: FeedbackBoard::extendTriage() for a custom AI driver, destinations.* for delivery, and the gate/resolver pair for all authorisation. Most customisation requests land on one of those three.
How can I help you explore Laravel packages today?