rupadana/filament-panel-switch
A Filament plugin that adds a panel switcher to your admin interface, letting users move between registered Filament panels without manually editing URLs. Useful for apps with separate admin, staff, or tenant panels. Install via Composer and register the plugin on your panel.
Minimal path from zero to a working panel switcher:
composer require rupadana/filament-panel-switch
php artisan vendor:publish --tag="filament-panel-switch-config"
Register the plugin on the panel(s) that should show the switcher, in the panel provider's panel() method:
public function panel(Panel $panel): Panel
{
return $panel
->id('admin')
// ...
->plugin(\Rupadana\PanelSwitch\PanelSwitchPlugin::make()); // copy the exact FQCN from the README
}
Then make sure the panels you want to switch to actually exist and are reachable by the same user. The single most important precondition is that all panels use the same auth guard (or that the user is valid on each guard) — otherwise "switching" is really "being logged out".
Where to look first
config/filament-panel-switch.php) — this is where the panel list, labels, icons and ordering live. Read it before touching any PHP.composer.json constraints as the authoritative source.First use case: a two-panel app (admin + app) where staff need to hop between a back-office panel and the end-user-facing panel without re-authenticating. Add the plugin to both panels, confirm both appear in the dropdown, and verify the switch preserves the session.
Register only on source panels. If a panel should never be a jumping-off point (e.g. a public marketing panel), simply don't register the plugin there. Registration is per-panel, not global — this is the primary lever for controlling blast radius.
Config-driven registry, not hardcoded links. Keep the panel list in the published config (id, label, icon, sort, optional color) rather than building your own ->userMenuItems() entries with url(). You get consistent ordering, and a single place to add a panel later. For anything dynamic (per-tenant panels, per-role visibility), prefer wrapping the config with your own service provider merge rather than editing the vendor file.
Gate visibility per user. Pair the plugin with your existing canAccessPanel() logic on each target panel. The switcher does not grant access — it only links. If a user shouldn't see a panel, hide the item (via config callback or plugin condition if exposed) and keep canAccessPanel() honest; the two should agree, otherwise users get a menu entry that 403s.
Share the guard, share the session. For multi-panel setups, standardise on one guard (->authGuard('web')) across panels unless you have a genuine reason not to. Document the guard matrix in the panel providers — this is the thing that breaks subtly in staging but not locally.
Combine with tenancy deliberately. If panels use different tenancy configurations (tenant(..., slugAttribute: 'slug') vs none), the generated URLs differ in shape. Test switching while a tenant is active in the source panel; the target panel may need a tenant context it doesn't have. Where tenancy differs, treat the switcher as "go to panel root" rather than "go to the equivalent page".
Keep it out of the critical path. The switcher is navigation sugar. Don't build redirect-after-login or authorization flows on top of it — use Filament's ->login() / ->registration() redirects and panel canAccessPanel() for that.
Test the round trip. A small Pest/PHPUnit test that hits each panel's root URL as an authenticated user is worth more than any amount of manual clicking, because it catches guard and route-name regressions introduced by unrelated panel changes.
composer.json (Filament constraint, PHP constraint) and grep your panel providers for the plugin FQCN and any fluent calls you use. Assume the plugin namespace, config keys, or registration signature may have changed — verify against the README rather than the release notes. Pin the version and upgrade in a branch.php artisan config:clear and, if you use Filament's optimisations, re-run your filament:optimize / optimize:clear cycle. Stale cached config is the #1 cause of "the new panel doesn't show up".php artisan route:list for the panel's routes.canAccessPanel() still wins. A visible menu item that leads to a 403 is a UX bug, not a security bug — but it looks like one to users. Keep visibility and access checks in sync.wire:navigate. If you enable Filament's SPA mode, cross-panel links point at different Livewire roots. If switching behaves oddly (stale page, double render), force a full page load for the switcher links.userMenuItems entry that reuses the plugin's panel list from config — keep your own copy of the URL-building logic in one helper so future major upgrades touch one file.bootstrap/providers.php / the panel provider list, (4) is config cached, (5) does the user pass canAccessPanel() on the target.How can I help you explore Laravel packages today?