filamerce/filament-user-profile
Filament User Profile adds a ready-made profile page to your Filament panel, letting users update their name, email, avatar, and password without extra scaffolding. Drop it into your panel and give every account self-service settings out of the box.
1. Install and publish
composer require filamerce/filament-user-profile
php artisan vendor:publish --provider="Filamerce\UserProfile\UserProfileServiceProvider"
php artisan migrate
Publishing typically gives you a config file, migrations (avatar/phone/profile columns on users, or a side table), and optionally views. Publish tags and exact provider FQCN should be confirmed from the package's src/ — with 0 stars and 0 dependents there is no community documentation to fall back on, so read the service provider's publishes() array first.
2. Register the plugin on the panel
// app/Providers/Filament/AppPanelProvider.php
public function panel(Panel $panel): Panel
{
return $panel
->id('app')
->plugin(\Filamerce\UserProfile\UserProfilePlugin::make());
}
Register it only on the panel where end users live (e.g. app), not on your admin panel, unless you want admins editing their own profile through the same screen.
3. Prepare the User model
Most packages in this space expect a trait and/or interface so the page can resolve the authenticated record and know which fields are fillable:
class User extends Authenticatable implements FilamentUser
{
use \Filamerce\UserProfile\Concerns\HasProfile; // verify name in src/Concerns
}
4. Where to look first
| Path | Why |
|---|---|
config/filament-user-profile.php |
Disk, allowed fields, enabled sections, route slug — the 80% knob |
src/UserProfilePlugin.php |
Fluent API for page label, slug, navigation, guards |
src/Pages/ |
The Livewire page(s); this is what you subclass or override |
database/migrations/ |
Confirm which columns are added before running in prod |
5. First use case
The canonical win: you have a Filament app with an admin panel and a customer-facing panel, and you need a "My Profile" page — name, email, avatar, password change, maybe account deletion — without hand-rolling a Livewire page, form schema, file upload handling, and password-confirmation rules. Add the plugin, register it on the app panel, hit /app/profile, done.
Keep the plugin configuration next to the panel it belongs to, and drive label/slug from config rather than hardcoding in views:
->plugin(
UserProfilePlugin::make()
->slug('account')
->navigationLabel('My Account')
->navigationGroup('Settings')
->navigationSort(99)
)
Because the page lives under the panel's auth guard, users must already pass canAccessPanel() — the profile page is not a login replacement.
The most common real-world need is "add my own fields to the profile form" (job title, timezone, notification prefs). Prefer the plugin's schema/extension hook over publishing and editing views:
UserProfilePlugin::make()
->form(fn (\Filament\Forms\Form $form) => $form->schema([
// ...package sections, then:
\Filament\Forms\Components\Select::make('timezone')
->options(config('app.timezones'))
->columnSpanFull(),
]));
If no such hook exists, subclass the page, override form(Form $form), and register your subclass in place of the default via the plugin's page-registration method. Check src/Pages/*.php for the actual method signature — this is the single most important thing to read before designing around the package.
Two viable shapes, and it's worth deciding up front:
Section components and put password behind ->dehydrated(fn ($state) => filled($state)) so an empty password field doesn't wipe the hash.// Password field pattern that is safe to leave blank
TextInput::make('password')
->password()
->revealable()
->dehydrated(fn ($state) => filled($state))
->rule(\Illuminate\Validation\Rules\Password::defaults())
->same('passwordConfirmation'),
Point the package at your disk and keep it consistent with the rest of the app so URLs don't break:
// config/filament-user-profile.php
'avatar' => [
'disk' => 'public', // or 's3' in multi-tenant/cloud setups
'directory' => 'avatars',
'max_size' => 2048, // KB
],
For existing apps that already store avatars (e.g. via Spatie Media Library), disable the package's uploader and render your own field instead of maintaining two sources of truth. Two avatar columns is the number one cause of "my photo disappeared" bugs.
->userMenuItems(['profile' => ...]) in the panel provider pointing at the profile page's route name.MustVerifyEmail on the model; the package should not be trusted to handle this, so assert it in your ProfileUpdated listener.$request->user() update logic should live in an action/service you can call from a controller too, so you don't end up with business rules embedded in a Livewire component.A Livewire smoke test is cheap insurance for a package with no maintainer community:
Livewire::actingAs($user)
->test(\Filamerce\UserProfile\Pages\Profile::class)
->fillForm(['name' => 'New Name'])
->call('save')
->assertHasNoFormErrors();
$this->assertSame('New Name', $user->fresh()->name);
Add a test for the password-untouched path and for email changed → verification reset. Those are where community profile packages regress.
Audit before you depend on it. Zero stars and zero dependents means zero battle-testing. Concretely: pin the exact version in composer.json, skim src/ end to end (it's a small package), and check the composer.json require block for filament/filament constraints. A package written for Filament v3 will install-but-misbehave on v4 (schema vs form APIs, Filament\Forms\Form vs Filament\Schemas\Schema). If the constraint is loose (^3.0) and you're on v4, treat it as unmaintained and fork.
Migrations will collide. Any app older than a year already has avatar, phone, or bio on users. Before php artisan migrate, open the published migration and either guard it:
if (! Schema::hasColumn('users', 'avatar')) {
Schema::table('users', fn (Blueprint $t) => $t->string('avatar')->nullable());
}
or delete the migration and reconcile by hand. Re-running in a fresh environment later with the original migration is a guaranteed duplicate column failure.
storage:link and disk visibility. Avatar uploads to the public disk silently 404 if php artisan storage:link hasn't run — and Filament's file upload will happily save the file and show the placeholder, so the bug looks like a UI issue. On S3, check the disk's visibility and URL expiry; signed-URL expiry is why avatars "disappear after an hour."
Email changes and verification. If users.email_verified_at exists, changing email should null it and re-send verification. Verify the package does this; if not, hook it:
protected static function booted(): void
{
static::updating(function (User $user) {
if ($user->isDirty('email')) {
$user->email_verified_at = null;
}
});
}
Multi-guard and multi-panel. The page resolves the user via the current panel's guard. If you have admin and web guards, or a panel using a non-default guard, confirm the plugin doesn't call auth()->user() (default guard) internally. Symptom: page loads but shows a blank form or throws on null user.
Cache and asset publishing after upgrade. php artisan filament:optimize-clear (or optimize:clear) after every composer update of a Filament plugin. Cached component/page registration is a classic "I edited the config and nothing changed."
Prefer hooks over published views. If you publish Blade views, you own them forever and upgrades won't reach you. Publish only the config; reach for ->form(), macros, or a page subclass for anything else. If you must override the view, pin the package version in the same PR.
Extension points to check for, in order: (1) config file for disk/fields/slug, (2) plugin fluent setters, (3) form/schema callback, (4) page subclass, (5) published views — last resort. Anything below (3) is a smell that you should just write the page yourself; a profile form is ~150 lines of Filament and you'd own it outright instead of inheriting an unmaintained dependency.
Decision heuristic. If the package's src/ reads clean and it exposes a form-extension hook, it's a reasonable time-saver for an internal tool. If it hardcodes its schema and publishes views, the total cost of ownership (fork, upgrade, debug with no upstream) exceeds writing the page by hand — and for a 0-dependency package, "write it yourself" is a legitimate, low-risk answer.
Debugging checklist when the page misbehaves: confirm the plugin is registered on the right panel (php artisan about or filament:optimize-clear then re-check); confirm the User model has the trait/interface; confirm the panel's guard matches the model's getAuthIdentifier; tail storage/logs/laravel.log while submitting — MassAssignmentException on avatar/phone is the most common first-run failure, fixed by adding the fields to $fillable (or $guarded = [] in dev).
How can I help you explore Laravel packages today?