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 User Profile Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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.


Implementation Patterns

Panel-scoped profile page

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.

Composing extra fields into the profile form

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.

Splitting "edit profile" from "change password"

Two viable shapes, and it's worth deciding up front:

  • Single page, sectioned form — simplest; use Section components and put password behind ->dehydrated(fn ($state) => filled($state)) so an empty password field doesn't wipe the hash.
  • Separate pages — cleaner audit story. The package usually ships a profile page plus a password page; enable/disable each via config so you don't present a password form in SSO-only tenants.
// 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'),

Avatar handling

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.

Wiring into the rest of the panel

  • Navigation: add a user-menu link rather than a sidebar item for consumer panels — ->userMenuItems(['profile' => ...]) in the panel provider pointing at the profile page's route name.
  • Onboarding: after email change, force re-verification by reusing MustVerifyEmail on the model; the package should not be trusted to handle this, so assert it in your ProfileUpdated listener.
  • Policies: the page resolves the current user, so no policy is strictly needed — but if you add an "edit another user" mode, add one.
  • API/mobile: treat this as a UI-only package. The underlying $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.

Testing

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.


Gotchas and Tips

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).

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager