Feedback by Carl Janzell
CommunityIn-app feedback for Filament panels: people tag the module they mean and how they feel, the team replies in a thread, and a board shows how a release landed.
Author:
Carl Janzell
Package health
Automated checks of this plugin's Composer package
15 checks
- Passed: GitHub Actions pinned to SHA
- Skipped: GitLab CI includes pinned to SHA
- Passed: Open security advisories
- Passed: Dependabot PR responsiveness — No open Dependabot PRs.
- Skipped: Renovate MR responsiveness
- Passed: Dependabot or Renovate configured
- Passed: Dependency update cooldown configured
- Passed: Provides a security policy
- Passed: Abandoned or archived — No consulted source marks the package abandoned (packagist, github).
- Passed: Commit and release recency — Active: last commit 0 days ago; last release 0 days ago.
-
Passed:
composer.lock not committed by library
—
composer.lockis absent from the released dist archive. - Passed: Dist archive is lean
-
Passed:
Current Laravel version supported
—
Package dependencies resolve together with current Laravel
13.0. -
Passed:
Current PHP version supported
—
Constraint
^8.2supports current PHP8.5. - Skipped: Current Symfony version supported
filament/
namespace. Review the source and install at your own risk. Found
malware or an unresolved security issue the author won't
address?
Report it
.
Documentation
In-app feedback for Filament panels. People press Feedback on any page, and a small sheet opens above the button: pick the parts of the app they mean and how they feel, and write. Their threads live on a My feedback page, with the team's replies, reactions and a Sent → Seen → Planned → Done timeline. The team reads every post in an inbox, replies and sets a status. A board shows the mood for each module, so you can see how a release landed.
- Works on Filament 3.3, 4 and 5 from one codebase.
- Four kinds of post: feedback, a question, a problem, or a note for the developers (which can be limited to the dev team and ping its own Chat space).
- Private by default: a post is seen by its author and the people you let triage.
- Modules stay compact: the page's module is pre-picked, the rest sit behind a search, and
typing
#DTR(or#BreakRoom,#dtr, …) in the message tags one with autocomplete. - Module chips use your app's own navigation icons.
- Reactions (Thanks, Same here, Seen, Fixed) and a "Did this fix it?" check once a post is Done.
- Replies send on Enter (Shift+Enter for a new line), and can be unsent (
allowUnsend()). - Stats on the inbox: posts, median first-reply time, what got done, and mood against the 30 days before.
- Motion by anime.js, loaded only when the viewer allows motion.
- Optional Google Chat ping for every new post.
Full documentation: https://darkify19.github.io/filament-feedback/
Pre-release. See ROADMAP.md for what's in, what's next and what 1.0 means.
#Screenshots
#Install
composer require carljanzell/filament-feedback:^0.4
php artisan migrate
php artisan filament:assets
#Set up
Register the plugin on each panel that needs it. A typical app has one panel everyone uses (the button) and an admin panel (the inbox):
use CarlJanzell\FilamentFeedback\FilamentFeedbackPlugin;
$modules = [
'dtr' => 'DTR',
'leave' => 'Leave',
'design' => ['label' => 'Overall look', 'icon' => 'heroicon-o-swatch'],
];
// The panel staff use: the floating Feedback button and My feedback, no inbox.
$panel->plugin(
FilamentFeedbackPlugin::make()
->modules($modules)
->moduleFromRoute(fn (?string $route): ?string => str_contains((string) $route, 'leave') ? 'leave' : null)
->inbox(false)
->chatWebhook(env('FEEDBACK_CHAT_WEBHOOK')),
);
// The admin panel: the inbox, no button.
$panel->plugin(
FilamentFeedbackPlugin::make()
->modules($modules)
->button(false)
->canTriage(fn (): bool => auth()->user()?->hasRole('admin') ?? false)
->navigationGroup('System'),
);
A one-panel app keeps both on (the default) and only sets canTriage().
| Method | Default | What it does |
|---|---|---|
modules(array|Closure) |
[] |
The parts of the app people can tag: slug => label, or slug => ['label' => …, 'icon' => …]. With none, the field is hidden. |
moduleFromRoute(Closure) |
none | Pre-selects a module. Receives $route and $url and returns a slug or null. |
canTriage(bool|Closure) |
false |
Who opens the inbox and replies as the team. |
button(bool|Closure) |
true |
The floating Feedback button on every page, and the My feedback page. A closure is checked on each page and hides only the button. |
inbox(bool) |
true |
The Feedback inbox page. |
launcherAttributes(array) |
[] |
Extra attributes on the button's wrapper, for your own CSS. |
impersonateUsing(?Closure $callback, bool|Closure $can = true) |
off | "View as" on an inbox thread: sign in as the author and open the page they wrote from. The callback does the impersonating with the app's own package (no dependency here), gets $author, $post, $url (on this app and still routed, else null: fall back to their home) and $guard, and returns a redirect, or false. $can gets $author; triagers only. |
chatWebhook(?string) |
none | A Google Chat space webhook. Each new post sends it one line. |
developerPosts(bool $enabled, bool|Closure $canRead, ?string $chatWebhook) |
on, every triager reads | The "For the developers" box. canRead limits who sees those posts in the inbox; chatWebhook pings the dev team's own space. |
allowUnsend(team: bool|Closure, authors: bool|Closure) |
team true, authors false |
Who may unsend their own replies. An unsent reply stays as "… unsent a message". |
navigationGroup(?string) |
none | Where the inbox sits in the navigation. |
Set it on the panel that has the button: posts are created there.
#Module icons
A module without an icon borrows one from your navigation: the plugin runs each page's and
resource's route through moduleFromRoute(), and the first page that maps to a module lends
it its navigation icon. So if moduleFromRoute() maps your DTR resource to dtr, the DTR
chip shows the same icon as the DTR item in the sidebar. An 'icon' in modules() wins.
#Moving the button
It sits bottom-left, and the sheet opens just above it. Shift both with CSS variables on
:root:
:root { --ffb-launcher-bottom: 5rem; --ffb-launcher-left: 1rem; }
#View as the author
The package doesn't impersonate anyone itself. Wire it to the package your app already uses; for example stechstudio/filament-impersonate:
use STS\FilamentImpersonate\Tables\Actions\Impersonate; // Actions\Impersonate on v4+
FilamentFeedbackPlugin::make()
->impersonateUsing(
fn (Model $author, ?string $url, string $guard) => Impersonate::make()
->guard($guard)
->redirectTo($url ?? '/')
->impersonate($author),
can: fn (): bool => auth()->user()?->hasRole(['hr', 'super_admin']) ?? false,
);
The button shows in the thread only for triagers who pass $can, never on their own posts.
While you're signed in as someone, the Feedback button would post as them. Hide it on the author's panel:
FilamentFeedbackPlugin::make()
->button(fn (): bool => ! app(\Lab404\Impersonate\Services\ImpersonateManager::class)->isImpersonating())
// filament-impersonate v4+: ! \STS\FilamentImpersonate\Facades\Impersonation::isImpersonating()
#Google Chat ping
In a Chat space: space name → Apps & integrations → Webhooks → add one, then copy
its URL into chatWebhook(). The app only calls out, so this works from a server Google
can't reach, and it needs no Cloud project. If the option is missing, your Workspace admin
has turned off webhooks.
#Stats
The inbox opens with CarlJanzell\FilamentFeedback\Widgets\FeedbackStats: posts in the last
30 days (with a 14-day sparkline), the median time to the team's first reply, posts done and
whether authors confirmed the fix, and the average mood against the 30 days before. Only
triagers see it. It is a plain Filament stats widget, so a dashboard can use it too, for
example filament-insights'
->widgets([FeedbackStats::class]).
#Hooking in
CarlJanzell\FilamentFeedback\Events\FeedbackPostedfires with$poston every new post. Listen to it to notify your triagers.CarlJanzell\FilamentFeedback\Events\ReplyPostedfires with$replyon both sides of a thread.$reply->is_from_teamtells you which side.- The author gets a Filament database notification when the team replies. Its link opens
that thread on My feedback (
/my-feedback?post=…). Turn on->databaseNotifications()on that panel.notifyAuthorUsing()receives the same$url. - A link to
#feedbackon any page of the button's panel opens the sheet.
#Data
Three tables: feedback_posts, feedback_replies and feedback_reactions. Authors are
polymorphic (morphs()), so any authenticatable model works, UUID keys included.
#For AI agents
The package ships an agent guide at
resources/boost/guidelines/core.blade.php: setup,
every plugin method, events, data and common tasks. Apps using
Laravel Boost pick it up automatically on
php artisan boost:install (or boost:update). Without Boost, point your agent at that file.
Working on the plugin itself: AGENTS.md.
#Testing
composer test
#License
MIT. See LICENSE.
The author
Carl Janzell is a full-stack software engineer based in Los Baños, Philippines. He builds the Laravel and Filament applications behind HR operations for more than 4,000 personnel at the University of the Philippines Los Baños, with systems that exchange data over Apache Kafka. He cares most about systems thinking, event-driven architecture, and tools that make admin panels faster to build and nicer to use.
From the same author
Featured Plugins
A selection of plugins curated by the Filament team
Soft Theme
A theme that gives panels a warm, approachable look with rounded shapes, gentle colors, and serif headings.
Filament
Advanced Tables (formerly Filter Sets)
Supercharge your tables with powerful features like user-customizable views, quick filters, multi-column sorting, advanced table searching, convenient view management, and more. Compatible with Resource Panel Tables, Relation Managers, Table Widgets, and Table Builder!
Kenneth Sese
Custom Fields
Eliminate custom field migrations forever. Let your users create and manage form fields directly in Filament admin panels with 20+ built-in field types, validation, and zero database changes.
Relaticle