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

stechstudio/filament-impersonate

A Filament plugin that lets admins impersonate users with a single action. Add Impersonate to your resource table or page header, with optional guard and redirect settings.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and align versions first. The plugin ships major lines that track Filament majors — the 4.x line landed as the "Filament V4 full implementation" and the 5.x line (currently 5.6.1) is what you want on newer Filament. Check the composer.json constraint in the repo against your installed filament/filament version before running composer require stechstudio/filament-impersonate; a mismatched major is the single most common source of confusing action/banner breakage on day one.

There is nothing to publish. Config is entirely ENV-driven, so a fresh install works immediately. The three files worth opening first:

  1. config/filament-impersonate.php — every knob you'd otherwise publish, including the FILAMENT_IMPERSONATE_ROUTE_PREFIX, soft-delete handling, and the banner render hook.
  2. STS\FilamentImpersonate\Actions\Impersonate — the action class you register in tables and page headers.
  3. STS\FilamentImpersonate\Facades\Impersonation — isImpersonating() / leave(), the two methods you'll sprinkle through policies, middleware, and Blade.

First use case: "log in as this customer to reproduce it." The 5-minute win is a support workflow — add a table action to UserResource, click the icon on a row, land in the app as that user, click "leave" in the banner to come back.

// app/Filament/Resources/UserResource.php
use STS\FilamentImpersonate\Actions\Impersonate;

public static function table(Table $table): Table
{
    return $table
        ->columns([/* ... */])
        ->actions([
            Impersonate::make(),
        ]);
}

Add the same action to the EditUser page header — and remember the record() call, which the README explicitly calls out:

protected function getHeaderActions(): array
{
    return [
        Impersonate::make()->record($this->getRecord()),
    ];
}

If your app is 100% Filament, you're finished — the banner is registered for you. The moment you have classic Blade layouts (marketing pages, legacy account area, PDF/export views), drop <x-impersonate::banner/> before </body> in each master layout, otherwise the developer gets stuck as the impersonated user with no way back except clearing cookies.

Implementation Patterns

Treat the action as a starting point, not the feature. In day-to-day use the raw Impersonate::make() is rarely enough for production; teams almost always wrap it:

Impersonate::make()
    ->label('Impersonate')
    ->guard('web')                       // explicit in multi-guard apps
    ->redirectTo(route('dashboard'))     // land somewhere deterministic
    ->visible(fn ($record) => auth()->user()->can('impersonate', $record))
    ->requiresConfirmation(),            // if your Filament version supports it on actions

Redirect deliberately, not implicitly. Default behaviour bounces to your app root, which in a multi-panel install may be the wrong panel. Set ->redirectTo() to the page the impersonator actually needs to inspect — usually that user's dashboard or the specific record you're debugging. Since 5.3.0 you can also force a full page load:

Impersonate::make()
    ->redirectTo(route('account.orders'))
    ->withoutSpa();   // required when the target isn't a Livewire-aware page

Gate both ends, always. Default behaviour allows any authenticated Filament user to impersonate anyone, which is unacceptable outside local/dev. Implement both hooks on the model:

class User
{
    public function canImpersonate(): bool
    {
        return $this->hasRole('super_admin');
    }

    public function canBeImpersonated(): bool
    {
        return ! $this->hasRole('super_admin')   // never impersonate peers/admins
            && ! str_ends_with($this->email, '@mycorp.com');
    }
}

Combine this with a policy check on the action's ->visible() so the icon doesn't render at all for unauthorised staff.

Use the facade as a session-state primitive. Impersonation::isImpersonating() is the right hook for anything that must behave differently while "wearing someone else's hat": disabling destructive actions, suppressing outgoing mail/SMS in staging, skipping analytics, or forcing read-only mode. Impersonation::leave() is useful in custom UI (your own banner variant, an API endpoint, a "return to admin" button in a mobile layout).

Build an audit trail — the package doesn't. There's no built-in logging, and "who impersonated whom, when, why" is usually a compliance requirement. The pragmatic pattern is a wrapping action that captures a reason and writes it before delegating:

Action::make('impersonate')
    ->form([Textarea::make('reason')->required()])
    ->action(function ($record, array $data) {
        activity()->causedBy(auth()->user())
            ->performedOn($record)
            ->withProperties(['reason' => $data['reason']])
            ->log('impersonation started');
        // then redirect into the plugin's route, or use the Impersonate action
    });

Pair it with a Logout/Login event listener so you can close the loop when leave() fires.

Multi-panel / multi-guard apps need explicit guard(). If you run an admin panel on web and a customer portal on customer, set ->guard('customer') on the action and verify the banner's redirect back goes to the panel you came from — this area has had repeated fixes (4.1.1 banner crash, 5.5.0 scoping login/logout impersonation clearing to the impersonator's guard), so pin a recent patch release.

Custom record impersonation (4.0.2+). When your impersonatable model isn't the actual auth provider (e.g. impersonating a Customer that maps to a User), don't fight the action — resolve the auth user yourself and hand the action the right record, or drive it through the facade/route directly.

Banner presentation. Keep it visually loud but non-blocking:

<x-impersonate::banner style="light" :display="auth()->user()->email" />

style accepts dark/light/auto, and :display overrides the name attribute lookup — useful when your impersonated model has no name column.

Testing. Impersonation is a redirect + session write, so feature-test it rather than unit-testing: actingAs($admin), hit the action's route/livewire() call, assert redirect + Impersonation::isImpersonating(), then hit the leave route and assert the original user is back. Also write a test asserting a non-admin cannot trigger the action.

Gotchas and Tips

The missing ->record(). The page action silently has no target if you omit it in getHeaderActions(). It's the #1 reported mistake because there's no exception — you just get a button that does nothing or throws on a null record.

403s from InteractsWithPageTable widgets. If a ListUsers page contains widgets using that trait, Livewire re-renders them before the redirect completes, and the impersonated (low-privilege) user gets a 403. The documented workaround is a guard clause in UserPolicy::viewAny():

public function viewAny(User $user): bool
{
    if (Impersonation::isImpersonating()) {
        return true;
    }
    // ...normal checks
}

This is a band-aid, not a fix — keep it narrow and comment why it exists so a future security review doesn't flag it as a hole.

SPA navigation will strand you. With ->spa() enabled, a redirectTo() pointing at a non-Livewire route renders an empty shell. Always pair such redirects with ->withoutSpa().

Soft-deleted targets are blocked as of 4.0. That's the right default (you can't impersonate a deleted user's session), but it will surprise you in tests using withTrashed() fixtures. Override only deliberately with FILAMENT_IMPERSONATE_ALLOW_SOFT_DELETED=true, and never in production.

Banner invisibility is almost always a layout problem, not a package bug. Outside Filament, nothing is auto-injected. Check: the component is in the layout actually used by the page, z-index isn't buried (3.6 lowered it below notifications intentionally), and if you moved it into a custom slot the render_hook config still points somewhere real. Older versions also fell back to the referer when fingerprint.path didn't exist — if the "leave" button returns you somewhere odd, that's the mechanism to inspect.

Config and route caching. Because everything is ENV-driven, php artisan config:cache will bake in stale impersonation settings. config:clear after editing .env. If you set FILAMENT_IMPERSONATE_ROUTE_PREFIX, remember the banner and any custom leave buttons must use the same prefix — hardcoding /impersonate/leave breaks silently.

Session lifetime during impersonation. 5.4.0 changed cookie expiration while impersonating; if you allow long-lived impersonation sessions, be conscious that the impersonated session can outlive the admin's original "remember me" window. For support workflows, prefer short sessions and make leave() prominent.

Don't nest impersonation. Impersonating while already impersonating loses the original identity and can trap you. Guard the action with ->visible(fn () => ! Impersonation::isImpersonating()) and always route exits through Impersonation::leave() rather than a plain logout — a plain logout destroys the admin's session entirely.

Version pinning matters more than usual here. The changelog read like a series of live bug fixes in exactly the areas you'll touch: 4.1.1 (multi-panel banner crash), 4.1.2 (redirects in modal/Livewire contexts), 5.5.0 (guard scoping), 5.6.1 (sidebar offset on panels without a topbar). Pin ^5.6 (or ^4.1.2 on Filament 4) rather than an open range, and re-test the banner after every Filament upgrade — the banner interacts with panel layout CSS and is the most fragile surface.

Extension points are thin by design. The package gives you an action, a banner component, a facade, and ENV config — nothing else. Anything richer (reason capture, approval flows, read-only impersonation, per-panel time limits, audit tables) is yours to build on top of Impersonation::isImpersonating() and your own middleware. Budget a day for production hardening: roughly an hour to wire up, and the rest for authorization, logging, and the policy guard clauses. Treat that as the real cost of the package, not the composer require.

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.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin