Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Impersonate Laravel Package

xlite-dev/filament-impersonate

Filament plugin to switch between accounts without signing out. Supports linked accounts, impersonation with a switch-back banner, and environment-gated developer logins, plus an audit trail and a fluent per-panel API. Formerly xlite-dev/filament-impersonate.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and scaffold.

composer require packstub/filament-account-switcher
php artisan packstub-account-switcher:install

The install command publishes config/packstub-account-switcher.php and the account_switches migration — run php artisan migrate before touching any feature, because the audit trail is written on every switch and a missing table will blow up mid-request rather than at boot.

2. Wire the model. HasLinkedAccounts is required for linked accounts (it provides linkAccount(), linkedAccounts, and the switcher menu data). The two authorization methods are optional but decide whether actions render at all:

use Packstub\AccountSwitcher\Concerns\HasLinkedAccounts;

class User extends Authenticatable implements FilamentUser
{
    use HasLinkedAccounts;

    public function canImpersonate(User $target): bool
    {
        return $this->hasRole('super-admin') && ! $target->hasRole('super-admin');
    }

    public function canBeImpersonated(): bool
    {
        return ! $this->is_locked;
    }
}

3. Register the plugin per panel. Features are opt-in flags on the plugin, not global config:

$panel->plugin(
    AccountSwitcherPlugin::make()
        ->impersonation()
        ->linkedAccounts()
        ->developerLogins(['[email protected]', '[email protected]']),
);

Where to look first: the published config file (it's short and documents the switch log and redirect defaults), then src/AccountSwitcherPlugin.php to see exactly which fluent methods exist in your installed version — the README lists the 4.x surface, but the class is the source of truth. Docs live in the repo under docs/.

First use case to ship: drop ImpersonateAction into your users table. It's a one-line change, immediately useful for support ("see what this customer sees"), and reversible via the banner — the lowest-risk way to validate the package before you touch linked accounts or dev logins.

->recordActions([
    ImpersonateAction::make(),
])

Implementation Patterns

Support impersonation, scoped by policy. Put the action on the users resource table and on the user view page header so support can impersonate from a ticket link without hunting rows. Keep the real authorization in a policy and delegate to it, so the same rule governs the action, your own endpoints, and any future UI:

AccountSwitcherPlugin::make()
    ->canImpersonateUsing(fn (User $by, User $target) => $by->can('impersonate', $target)),

This matters because the README's default check is the model method — fine for a small app, wrong once you have roles seeded across teams.

Linked accounts as the day-to-day identity model. The high-value workflow is: everyone works in a low-privilege account, and admin access is an escalation, not a login. Seed the links so you don't click through the Linked accounts page per developer:

// in a seeder, after both users exist
$admin->linkAccount($daily, label: 'Daily', requiresPassword: false); // down: one click
$daily->linkAccount($admin, label: 'Admin', requiresPassword: true);  // up: password prompt

Note the direction is explicit per link — you decide which side is cheap. The password prompt asks for the target account's password, which is the whole security argument: a stolen daily session can't reach admin. If your admins authenticate via SSO and have no usable password, mirror the links so the escalation path is the one-click side, or set requiresPassword: false and rely on canSwitchUsing() for the gate instead.

Gate switches with a closure when "linked" isn't the same as "allowed." canSwitchUsing() runs on the switch path and is where suspension, contract expiry, or "no switching during an active incident" belongs:

->canSwitchUsing(fn (User $from, User $to) => ! $to->is_locked && $to->tenant_id === $from->tenant_id)

Use the audit trail as an integration point, not just a table. Every switch writes to account_switches (actor, target, reason, panel, guard, IP, user agent) and fires AccountSwitching (pre, cancellable) and AccountSwitched (post). Two patterns pay off:

  • Listen to AccountSwitching and throw/deny for blocked targets — this is the last line of defence before the session changes, and it covers every entry point (menu, action, developer login) at once.
  • Listen to AccountSwitched to notify the target user ("an admin accessed your account") and to mirror the row into your SIEM. Queue the listener; it runs inside the switch request.

Developer logins are a local-ergonomics feature — treat them as such. They render only in the environments in the config (local by default) and only for the emails you pass to the plugin. Keep the email list in the plugin call, not the config, so a stray config publish can't leak a list of privileged accounts into staging. Pair with seeded users keyed off the same env check:

->developerLogins(app()->isLocal() ? ['[email protected]', '[email protected]'] : [])

Per-panel composition. Because the plugin is registered per panel, the natural split is: full impersonation + linked accounts on the admin panel, impersonation only on a support panel, developer logins on none. Use banner(position: 'top', style: 'light') to move the impersonation banner if the default bottom placement collides with your own footer/chat widget, and switcherRenderHook() when you want the "Switch to" menu somewhere other than the topbar.

Testing. The migration is part of the package, so RefreshDatabase plus Event::fake([AccountSwitched::class]) gives you a clean assertion on any switch path. Assert both the session user and the log row — a switch that changes the session but skips the log is exactly the bug you want the suite to catch.

Gotchas and Tips

The package was renamed — most of the internet is wrong. Search results and blog posts point at xlite-dev/filament-impersonate, the 3.x line. In 4.x the namespace is Packstub\AccountSwitcher\*, the config file is packstub-account-switcher.php, and the Blade banner component is gone. If a tutorial tells you to composer require xlite-dev/filament-impersonate or use <x-filament-impersonate::banner />, you're on a different package. Read UPGRADE.md before migrating anything real.

Don't depend on lab404/laravel-impersonate. 4.x dropped it, so its helper functions, middleware, and impersonate() route macros are not available. Build "leave impersonation" against the package's own POST route, filament.{panel}.account-switcher.switch-back — note it moved from GET to a CSRF-protected POST inside the panel's authenticated group, so any hand-rolled "switch back" link you carried over from 3.x is now a 405.

Config keys that no longer exist. guard, redirect_to, and leave_middlewares were removed; the panel's guard and routes are used instead. Use ->redirectTo('/admin') for the post-switch destination, and be aware the package already resolves a redirect to a panel the target may access — overriding it with a hard path that the target cannot access is a reliable way to produce a redirect loop or a 403 right after a successful switch.

Version-pin the two cosmetic bugs. 4.0.1 fixed an SvgNotFound thrown at render time because the icon set was registered with a hyphenated prefix (the icon is now packstub:account:switcher-icon — if you reference it yourself, use exactly that). 4.0.2 fixed banner(position: 'top') covering the topbar by re-anchoring Filament's sticky container and sidebar. Require ^4.0.2; on 4.0.0 the banner is visibly broken in a way that looks like your CSS.

Tenant panels: the Linked accounts page lives under {tenant}. 4.0.4 stopped throwing Missing required parameter … [Missing parameter: tenant] on tenant-free pages (registration, /app/new) by omitting the "Manage linked accounts" user-menu item there. If you add your own link to linkedAccountsPage() in a custom widget or a tenant-registration page, you'll reintroduce that exact exception. Switching between already-linked accounts still works on those pages — only the management page is unreachable.

Authorization methods are load-bearing, and absence is silent. If neither canImpersonate() nor canImpersonateUsing() is defined, the action's visibility is governed by the package default, not by your roles. A "super-admin only" expectation that is only written in a comment is a production hole. Define both the model method and the closure, and test that a plain user gets no action on the row.

The audit table grows and contains personal data. It stores IP and user agent per switch — fine for a security review, but it makes account_switches a retention and GDPR conversation. Decide a retention window and a prune job at install time, not after 200k rows. Also confirm with legal whether the "reason" field can hold free text from support agents.

A switch is not a login event for your existing listeners. Login/Authenticated events won't fire the way you expect on a switch, so anything you attach to login (last-seen updates, MFA re-checks, device fingerprinting) silently doesn't run. If your app does security-relevant work on login, hook AccountSwitched explicitly.

Debugging a switch that "does nothing": check, in order — the migration ran (account_switches exists), the trait is on the model, the plugin is registered on that panel (not just the default panel), and canSwitchUsing/canImpersonate isn't returning false for the pair. All four fail quietly with no visible UI change, which is why the switch log is your first diagnostic: if a row exists, the switch succeeded and your problem is the redirect; if not, it was denied before the session changed.

After publishing config or changing the plugin call, clear caches (php artisan config:clear, and filament:optimize-clear if you've cached components). The fluent API is evaluated at panel boot, so a stale config cache makes plugin changes appear to be ignored.

Extension points worth knowing before you fork: createSubAccountUsing() to override how sub-accounts are created (e.g. invite-email instead of instant creation), linkedAccountsPage() to swap in your own page class, switcherRenderHook() to reposition the menu, and the AccountSwitcher facade for programmatic switching in console commands or tests. Reach for these before copying package code into app/ — the fluent surface is broad enough that most "we need to customise this" cases are a method call.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel