joaopaulolndev/filament-edit-profile
Filament Edit Profile adds a ready-to-use profile page to Filament panels, letting users update their name, email, password, and avatar. It integrates cleanly with Filament, offers customizable forms and translations, and helps you ship account settings faster.
This is a small, single-purpose package: it gives a Filament panel a ready-made "Edit Profile" page (avatar, name, email, password, optional account deletion, and — as of v1.0.48 — a "log out other browser sessions" action) so you don't hand-roll one. At ~263 stars and a single maintainer, treat it as thin glue you could own, not critical infrastructure — which is fine, because its whole surface area is a trait, a page class, a plugin class, and a migration.
Install and publish
composer require joaopaulolndev/filament-edit-profile
php artisan vendor:publish --tag="filament-edit-profile-config"
php artisan vendor:publish --tag="filament-edit-profile-migrations"
php artisan migrate
php artisan storage:link # required for avatar uploads
Wire the trait onto your authenticatable model
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasAvatar;
use Joaopaulolndev\FilamentEditProfile\Concerns\HasProfile;
class User extends Authenticatable implements FilamentUser, HasAvatar
{
use HasProfile; // provides the avatar URL accessor the panel expects
}
Register the plugin on the panel
use Joaopaulolndev\FilamentEditProfile\FilamentEditProfilePlugin;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugin(
FilamentEditProfilePlugin::make()
->setNavigationLabel('Edit Profile')
->setNavigationGroup('Settings')
->setIcon('heroicon-o-user-circle')
->setSort(10)
);
}
If you want "log out other sessions" (v1.0.48+), do this before you ship
\Illuminate\Session\Middleware\AuthenticateSession::class to the panel's middleware stack — without it the button appears to work and logs nobody out.Where to look first, in this order
config/filament-edit-profile.php — the section toggles (avatar/name/email/password/delete-account/logout-other-sessions) are the whole feature set. Read this before writing any code.vendor/joaopaulolndev/filament-edit-profile/src/FilamentEditProfilePlugin.php — the fluent setters for title, slug, navigation group/label/icon/sort, and per-section visibility. Fluent method names have shifted between package majors, so trust this file over the README.src/Pages/EditProfile.php — the Livewire page and its form schema; this is what you subclass when you need extra fields, and where the logout-other-sessions action lives.HasProfile trait — confirms which column it reads for avatars (avatar_url) and which storage disk it uses.First use case to ship: stop forcing users to log out to change a password. Register the plugin, add a user-menu link, and you have a working profile screen in an afternoon. The logout-other-sessions button is the natural second ship — it turns the profile page into the place users go when they suspect a shared or stolen laptop.
Most apps don't want "Edit Profile" as a permanent sidebar item. Hide it from navigation and hang it off the avatar dropdown instead:
use Filament\Navigation\MenuItem;
use Joaopaulolndev\FilamentEditProfile\Pages\EditProfile;
->userMenuItems([
'profile' => MenuItem::make()
->label('My profile')
->icon('heroicon-o-user-circle')
->url(fn (): string => EditProfile::getUrl()),
])
Pair that with ->shouldRegisterNavigation(false) on the plugin. The page still resolves by URL and keeps its own title/breadcrumbs.
The plugin is registered per panel, so this is the natural place to differentiate surfaces — e.g. a customer-facing panel gets avatar + name + password + logout-other-sessions, while an internal admin panel disables email changes (identity comes from SSO) and disables account deletion:
// admin panel
FilamentEditProfilePlugin::make()
->setNavigationLabel('My Account')
// section toggles per config/plugin API in your installed version
Keep the toggles in the published config as the baseline and only override per panel where it genuinely differs. Two sources of truth for the same toggle is how you get a "why is the password field back?" bug report.
This is a port of the Filament 3 implementation and is described as driver-independent, which in practice means it uses Laravel's own session-invalidation mechanism rather than querying a sessions table. Two things follow from that:
Keep the UX honest: after a successful click, show a notification along the lines of "Other browsers have been signed out. This browser stays signed in." That single sentence heads off most support tickets.
If you only want the behaviour and not the UI, you can call Laravel directly from your own action and skip the package section entirely:
use Illuminate\Support\Facades\Auth;
Auth::logoutOtherDevices($currentPassword);
That's the whole feature. If your panel's profile page is already customised, this is often the cheaper path.
The page is a normal Filament page with a form schema, so the practical extension point is subclassing and re-registering your subclass (or publishing/overriding the view if you only need presentation changes):
namespace App\Filament\Pages;
use Filament\Forms\Components\TextInput;
use Joaopaulolndev\FilamentEditProfile\Pages\EditProfile as BaseEditProfile;
class EditProfile extends BaseEditProfile
{
// override the form schema to append job title, timezone, locale, etc.
}
For anything beyond one or two fields — notification preferences, team membership, API tokens, active-session listings — stop extending and build a dedicated settings page. The value of this package is that it's disposable; don't turn it into a god page. The logout-other-sessions button is a good litmus test: it's a single destructive action, so it fits; a table of individual sessions with per-device revoke does not, and belongs in its own page.
Confirm three things and avatar uploads just work: the avatar_url column exists (from the published migration), the disk is publicly readable, and the accessor resolves through Storage::url(). If you serve assets from S3/CloudFront, set the disk's url correctly rather than string-concatenating paths — otherwise you get signed-URL churn or 403s in production while local dev looks perfect.
If you need thumbnails or conversions, don't fight the package: keep the original upload path in avatar_url and generate derivatives in an observer or a saved hook.
A common pattern: force users with no name or avatar to the profile page once. Do it with a small middleware on the panel's route group rather than editing the page's mount() — it keeps the redirect logic testable and out of vendor code.
Treat this as a destructive admin action, not a profile feature:
HasApiTokens, revoke tokens on delete.deleting/deleted model events for related-record cleanup rather than relying on the page.If any of that sounds like work you don't want to own, disable the section in config and point users at a support flow.
The page is a standard Livewire component:
Livewire::actingAs($user)
->test(EditProfile::class)
->fillForm(['name' => 'New Name'])
->call('save')
->assertHasNoFormErrors();
$this->assertDatabaseHas('users', ['name' => 'New Name']);
Use Storage::fake() around avatar tests, and add one test asserting a different user's record is never touched — the classic bug in hand-rolled profile pages is trusting a form field for the user ID.
For the logout-other-sessions action, assert behaviour rather than the notification: create a second session (or set a stale password_hash_web in the session), fire the action, and assert the other session is invalidated. Mocking the session store is usually easier than driving two browser-like tests.
"Log out other sessions" silently does nothing without AuthenticateSession. Laravel invalidates other sessions by regenerating the session's password hash and comparing it in middleware. No middleware, no invalidation — the action returns success and everyone stays logged in. Put \Illuminate\Session\Middleware\AuthenticateSession::class on the panel's middleware and verify manually with two browsers before you tell anyone the feature works.
It's a Filament 3 port. v1.0.48's note says "(Filament 3)" explicitly. If you're on Filament 4, check the plugin class and the changelog before assuming the toggle exists or behaves identically — the feature may lag across majors.
Don't double-implement HasAvatar. If your User already implements Filament\Models\Contracts\HasAvatar with its own getFilamentAvatarUrl(), adding the trait collides. Pick one; if you already have avatar logic, skip the trait and let the package write to your existing column (and adjust the migration accordingly).
The migration assumes a users table with the usual columns. Custom table names, UUID/ULID keys, or an existing avatar column mean editing the published migration before running it. Never run it blind on a database with real users in it.
Filament v4 changes the calculus. Filament v4 ships a first-party profile page, so on a greenfield v4 project you should evaluate whether this package adds anything beyond what ->profile() gives you — and whether the v1.0.48 session feature is even reachable there. Check the package's composer.json constraint against your Filament major before adopting, and don't mix package majors across Filament majors.
Email changes and verification are the sharp edge. If your model implements MustVerifyEmail, decide explicitly whether changing the email resets email_verified_at — the package may or may not do this for you. Also verify the unique-email validation rule ignores the current user's record; a "this email is taken" error on saving your own unchanged email is the most common complaint with profile screens generally.
Passwords: know whether you're hashing once or twice. If your model casts password => 'hashed', assigning the plain value is correct. If it doesn't, the page must hash. Don't add Hash::make() on top of a hashed cast — you'll silently lock users out, and it only shows up on next login. This bites hardest with the logout-other-sessions action, which re-hashes the password internally: if your cast situation is wrong, the "current password" confirmation can fail for a user who typed it correctly.
Logging out other sessions does not log out API clients. Sanctum tokens, personal access tokens, and remember_token cookies are separate concerns. If a user clicked the button because they lost a device, tell them (in UI copy) to also revoke tokens, or wire the revocation into the same action.
Authorization is "any authenticated panel user." That's usually right for a profile page, but if you need to restrict it (e.g. read-only impersonation sessions), override canAccess() in a subclass rather than adding middleware. The logout-other-sessions action deserves the same thought: an impersonating admin clicking it would be a bad day.
Tenancy is user-global, not tenant-scoped. In a ->tenant() panel, this page edits the User, not the user's membership in the current tenant. That's usually what you want, but it surprises people who expect role/tenant settings to live there.
Slug collisions. If you already have a profile-ish page (Breezy, a custom Profile page, or Filament v4's built-in), two pages claiming edit-profile gives confusing 404s or the wrong page winning. Grep your panel provider for existing profile routes.
Avatar URLs break in production far more often than in dev. storage:link missing, APP_URL wrong behind a proxy, or ASSET_URL unset with a CDN — all produce the same "broken image" symptom with no error in the logs. Check Storage::disk(...)->url($path) output first.
Version-pin and read the plugin class. The fluent API (navigation setters, section toggles, title/slug) has changed shape between releases. Pin the version in composer.json, and when upgrading, diff src/FilamentEditProfilePlugin.php and src/Pages/EditProfile.php — that pair is the real changelog. v1.0.48 is a good example: a user-visible feature landed with a one-line release note and no migration or config diff called out.
Sanctum tokens section (if present in your version) requires Laravel\Sanctum\HasApiTokens on the model, and plaintext tokens are shown exactly once. Make sure your UI copy says so — and note the pairing with the new "log out other sessions" action above.
Localization: publish the translation tag if you need to override labels; otherwise you're editing vendor files and losing them on the next composer update. The new session-logout strings are a common first target.
Exit strategy is cheap — keep it that way. The entire integration is a trait, a plugin registration, and one migration. Don't scatter references to EditProfile::getUrl() across your codebase; wrap it in a single helper or route name so swapping to Filament's built-in profile page later is a one-file change. The session-logout action is the one piece you may end up keeping even after you drop the package — it's three lines of Auth::logoutOtherDevices().
How can I help you explore Laravel packages today?