jeffersongoncalves/filament-help-desk
Filament plugins that add User, Operator, and Admin panels for ticket management on top of jeffersongoncalves/laravel-help-desk. Includes publishing for config, views, and translations. Requires PHP 8.3, Laravel 11.28, and Filament 5.
1. Confirm compatibility before touching composer. The plugin tracks Filament majors aggressively: 3.x requires Filament 5 / Laravel 11.28 / PHP 8.3, while 2.x is Filament 4 and 1.x is Filament 3. Match the plugin version to the Filament version already in your app — do not let Composer resolve "latest" and accidentally drag Filament forward. Features also ship per branch: the 1.10.0 release (company names, category hierarchy) landed on the 1.x line, so check the CHANGELOG.md of your own branch before assuming parity.
composer require jeffersongoncalves/filament-help-desk:^3.0
2. Install the core domain package first. This repo is only the UI layer; tickets, messages, categories, and policies live in jeffersongoncalves/laravel-help-desk. Publish and run its migrations/config before registering any panels. Some UI features have a hard floor on the core version — company names in 1.10.0 require core ^1.13, and that upgrade brings its own migration (the denormalised company_name on tickets):
composer require "jeffersongoncalves/laravel-help-desk:^1.13"
php artisan vendor:publish --tag="help-desk-migrations" # verify the exact tag in the core README
php artisan migrate
3. Where to look first. Open the package's src/ and identify whether it ships panel providers (standalone User/Operator/Admin panels) or a Filament plugin class you attach to an existing panel. The README's Installation section is the source of truth; the class names below are typical for this author's packages, so verify them with composer show --tree jeffersongoncalves/filament-help-desk and the source tree before copying. Then register either the providers in bootstrap/providers.php, or the plugin in your panel:
// bootstrap/providers.php (if standalone panels are shipped)
return [
App\Providers\AppServiceProvider::class,
JeffersonGoncalves\FilamentHelpDesk\Providers\UserPanelProvider::class,
JeffersonGoncalves\FilamentHelpDesk\Providers\OperatorPanelProvider::class,
JeffersonGoncalves\FilamentHelpDesk\Providers\AdminPanelProvider::class,
];
// app/Providers/Filament/AdminPanelProvider.php (if it exposes a plugin)
use JeffersonGoncalves\FilamentHelpDesk\HelpDeskPlugin;
public function panel(Panel $panel): Panel
{
return $panel->plugin(HelpDeskPlugin::make());
}
4. First use case — the three-lane workflow. End users open and reply to their own tickets in the User panel, support staff triage and respond in the Operator panel, and admins manage categories, SLAs, canned responses, and assignments in the Admin panel. Boot all three, log in as a seeded user in each, and create one ticket end-to-end. That single walkthrough will surface 90% of configuration issues (guards, panel access, policies, migrations) at once. If you support multiple companies, extend the walkthrough: seed two companies, one user with company_name and one without, and file a ticket as each — the Admin/Operator list, the company filter, and the ticket detail should all show a readable name, not a raw id.
Separate panels, separate guards. Give each panel a distinct path (/support, /agent, /admin) and, ideally, distinct route middleware/domains. If all three share the web guard, you must implement FilamentUser::canAccessPanel() on your User model to differentiate roles:
public function canAccessPanel(Panel $panel): bool
{
return match ($panel->getId()) {
'admin' => $this->hasRole('admin'),
'operator' => $this->hasAnyRole(['admin', 'operator']),
'user' => true,
};
}
Otherwise every authenticated user can reach every panel. Use spatie/laravel-permission or a single role column — the plugin does not invent a role system for you.
Multi-company portals: expose company_name on your User model. The Admin and Operator panels render the company name in the column, the filter, and the ticket detail. Provide it either as a real column or as an accessor over company_id — the accessor route is the low-effort option when you already have a Company relation:
// app/Models/User.php
protected $appends = ['company_name'];
public function getCompanyNameAttribute(): ?string
{
return $this->company?->name;
}
Treat the ticket's company_name as a snapshot, not a lookup. The value is copied onto each ticket at creation time. That is deliberate — a ticket's history should not silently change when a company is renamed — but it means reports grouped by company name will contain both old and new spellings after a rebrand. If you need stable grouping, filter/report on company_id, and normalise the display string in a one-off backfill when you rename. Users without a company fall back to showing the raw company_id, which is your signal that data is missing rather than a rendering bug.
Lean on the category hierarchy instead of a flat taxonomy. Since 1.10, child categories are grouped under their parent in the ticket form, grandchildren render as Child › Grandchild, and a parent with active children can no longer be selected — enforced both in the form and in the plugin's server-side validation. So model your categories as: parent = grouping/display node, leaf = the only thing a user can actually file against. Seed accordingly, and give every parent at least one child, otherwise it stays selectable as a leaf. Existing tickets filed under a now-parent category are left alone on edit rather than being invalidated — keep that in mind when you re-run a category seeder on a live database.
Reuse the admin panel instead of standing up a third one. Many teams only need Operator + User panels and prefer to fold ticket administration into their existing Filament admin. Register the plugin (or just the TicketResource / CategoryResource classes) into the panel you already have, then ->navigationGroup('Help Desk') and ->navigationBadge() for open-ticket counts. This avoids three themes, three asset builds, and three login pages.
Scope widgets per panel. The bundled stats widgets are the most-edited files in real deployments. Override the query scope so the User panel only shows my tickets and the Operator panel shows my assigned queue vs. unassigned. Subclass the widget or publish views and adjust the base query; the plugin's widgets are generic by design. Multi-company installations usually want a further company_id scope here so operators don't see cross-tenant counts.
Wire notifications through Laravel, not the panel. Trigger TicketCreated / TicketReplied notifications from observers on the core models (or listen to core events) and let Filament's database notifications display them in the bell. Keep mail/queued notifications in the domain package layer so they survive an operator using the API instead of the panel.
Extend resources via configuration before subclassing. Look for configureUsing() hooks or a config file that lets you swap the resource's model, form schema, or table columns. If you need a tenant/organization filter, add a global scope on the Ticket model rather than editing the resource — it will apply everywhere (widgets, relation managers, policies) at once. Prefer that scope over hand-editing the company column/filter the plugin now ships.
Authorize with the core policies. Filament respects TicketPolicy / CategoryPolicy if they are registered and namespaced correctly. Confirm Gate::policy() bindings in the core service provider before writing custom ->authorize() closures; duplicate authorization logic is the usual cause of "operators can see tickets they shouldn't."
composer.json, and read CHANGELOG.md before every bump. Note that pinning cuts both ways: 1.10.0's company-name feature requires core ^1.13, so a plugin bump forces a coordinated core bump and migration. Be prepared to fork resources rather than wait for a fix.php artisan migrate is the most likely source of a new error after this release. Run php artisan vendor:publish with no tag and inspect the list of available tags.company_name missing shows the id. If Admin/Operator displays something like 42 in the company column, your User model has neither a company_name column nor an accessor. This is a display fallback, not an exception — add the accessor rather than patching the resource.admin. If two providers both register /admin, only one wins silently. Explicitly set ->id() and ->path() on each panel.canAccessPanel returning true by default. Filament's default is permissive. Always override it, and test with a non-staff account; "operator can access admin panel" is the classic first-day bug.php artisan filament:assets, php artisan optimize:clear, and php artisan filament:cache-components. If a resource disappears from navigation after a config change, clear the component cache before debugging further.composer update — and check whether a feature you're relying on (like the category grouping or company name) has been ported forward yet.php artisan about lists registered panel providers; an empty or duplicated entry tells you immediately that the provider wasn't autoloaded or a panel ID clashed.--tag=filament-help-desk-config) and views (--tag=filament-help-desk-views) even if you only plan to tweak one widget — it makes upgrades a diff instead of a rewrite.MAIL_* config looks like "the plugin doesn't notify," but the panel has no involvement. Check failed_jobs first.TicketPolicy, widget query overrides, and published Blade views — in that order. Avoid editing vendor/; with this package's release cadence, your patch will be overwritten.How can I help you explore Laravel packages today?