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

jeffersongoncalves/filament-user

Filament User adds ready-made user management to your Filament panel: profile editing, avatar uploads, password changes, and a full user resource. Drop-in install, configurable fields, and styling that matches Filament out of the box.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a starter-kit building block: it ships a User model, a Filament UserResource, a status-aware login page, and a panel plugin that wires them together. Treat it as an opinionated baseline you can adopt wholesale (fresh project) or cherry-pick (existing project).

Install and publish

composer require jeffersongoncalves/filament-user

# Discover what's publishable first — tags vary between releases
php artisan vendor:publish --provider="JeffersonGoncalves\FilamentUser\FilamentUserServiceProvider"

# Typical tags
php artisan vendor:publish --tag="filament-user-config"
php artisan vendor:publish --tag="filament-user-migrations"
php artisan vendor:publish --tag="filament-user-translations"   # only if you override copy
php artisan migrate

You only need the translations tag if you're overriding strings. The package ships 19 locales out of the box (as of 3.2.0), so most apps should not publish them — publishing freezes them and you'll miss future translation fixes.

Register the plugin in your panel provider:

use JeffersonGoncalves\FilamentUser\FilamentUserPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->id('admin')
        ->path('admin')
        ->login()
        ->plugin(FilamentUserPlugin::make());
}

Where to look first, in this order:

  1. src/FilamentUserPlugin.php — the fluent API and what it registers.
  2. config/filament-user.php — model class, table name, status values, login behavior.
  3. src/Models/User.php — casts, fillable, status scopes/helpers.
  4. src/Filament/Resources/UserResource.php — form schema, table columns, actions.
  5. resources/lang/{locale}/resources/user.php — the translation tree (note the nested resources/ path as of 3.2.0).
  6. The login page class — this is the package's differentiator and the most likely place you'll need to override.

First use case: stand up an admin panel on a fresh Laravel app, create a user (php artisan make:filament-user or via the resource), then flip that user's status to an inactive value and confirm the login page rejects them. That single round-trip exercises the model, resource, and login gate together.

Implementation Patterns

Adopt the whole stack (greenfield). Let the package own App\Models\User. Point config/auth.php at the package model (or a thin subclass in app/Models/User.php that extends it), and leave the migrations published from the package. This is the intended path for the starter kits.

Extend, don't fork (brownfield). If you already have a User model, subclass the package model and override only what differs:

namespace App\Models;

use JeffersonGoncalves\FilamentUser\Models\User as BaseUser;

class User extends BaseUser
{
    protected $fillable = [/* your columns merged with status */];

    public function team(): BelongsTo { /* ... */ }
}

Then repoint config/filament-user.php and config/auth.php at App\Models\User. Keeping the subclass in app/ means package upgrades don't clobber your customizations.

Customize the resource via the plugin, not by editing vendor files. Most of these plugins expose fluent setters; use them in the panel provider:

->plugin(
    FilamentUserPlugin::make()
        ->userResource(UserResource::class)
        ->navigationGroup('Access Control')
)

If a setter doesn't exist, subclass the resource and register the subclass — that's the stable extension point.

Status semantics. Model the status as a backed enum if the package doesn't already, and centralize the "can log in" rule in one place:

public function canLogin(): bool
{
    return $this->status === UserStatus::Active;
}

Then have both the login page and any policy/middleware call canLogin(). Avoid scattering status === 'active' string comparisons — that's the most common source of "user is active but can't log in" bugs.

Localization (new in 3.2.0). The package now ships 19 locales with a test guaranteeing key parity against en. Two practical implications:

  • Set app()->setLocale() per user/panel as you normally would and the resource + login page follow. Nothing extra to wire.
  • To override individual strings, prefer a namespaced translation override in your app rather than publishing the whole tree — e.g. drop a file at lang/vendor/filament-user/{locale}/resources/user.php containing only the keys you want to change. Laravel merges it over the package file, so you keep receiving upstream translations for everything else.

If you do need the full file, publish with --tag="filament-user-translations".

When adding a locale upstream doesn't cover, mirror the en file structure exactly and run the package's parity expectation yourself — the shipped test only covers the locales the package owns.

Layer authorization on top. The package gives you a user CRUD surface, not a permission system. Pair it with spatie/laravel-permission + filament-shield, or at minimum register a UserPolicy. Do not rely on the shipped resource being gated by default.

Multi-panel setups. Register the plugin only on panels that should expose user management. For a customer-facing panel, you usually want ->login() plus the status-aware page but not the UserResource; check whether the plugin lets you disable the resource, otherwise register it conditionally per panel.

Seed and test deliberately. Write a factory state per status so login tests are trivial:

User::factory()->inactive()->create(['email' => '[email protected]']);
$this->post('/admin/login', [...])->assertSessionHasErrors();

Use Filament's livewire() test helpers to exercise the UserResource table and form rather than raw HTTP assertions. On 3.2.0+ you can also assert against translated labels rather than hardcoded English, which keeps your tests locale-agnostic.

Upgrade workflow. With 2 stars and 0 dependents, this is effectively a single-maintainer package. Pin the minor version in composer.json ("^3.2", not "*"), and diff the published config and any published lang files after each upgrade before running migrations.

Gotchas and Tips

3.2.0 renames every translation key — this is a breaking change. The lang file moved from resources/lang/{locale}/user.php to resources/lang/{locale}/resources/user.php, and the key namespace changed with it:

Old New
filament-user::user.model_label filament-user::resources/user.navigation.singular
filament-user::user.plural_model_label filament-user::resources/user.navigation.label
filament-user::user.navigation_group filament-user::resources/user.navigation.group
filament-user::user.email_copied filament-user::resources/user.actions.email_copied
filament-user::user.fields.* filament-user::resources/user.fields.*

If you published translations in an earlier version, --force republish, then re-apply your customizations on top of the new structure. If you overrode keys in code (__('filament-user::user.fields.name') in a custom column, a policy message, or a Blade view), those calls silently resolve to the raw key string after upgrade — no exception, just filament-user::user.fields.name rendered on screen. Grep your app/, resources/, and lang/ for filament-user::user. before bumping. Note the path segment is a literal directory name containing a slash, which surprises people expecting dot notation everywhere.

Migration collisions are the #1 failure. If your app already has a users table, the published migration will fail or, worse, silently no-op on some drivers. Either skip publishing migrations and hand-add only the status column, or guard with Schema::hasTable('users'). Read every published migration before running migrate.

Config caching bites immediately. After editing config/filament-user.php (especially the model class or status list), run php artisan config:clear. A cached config pointing at a stale model class produces confusing "class not found" or "column not found" errors that look like package bugs. Translation files are not config-cached, but if you deploy with config:cache and your lang overrides live in a non-standard path, verify the loader still finds them.

The login gate fails closed and quietly. Status-aware login pages typically throw a generic validation error to avoid leaking account state. If a legitimate user can't log in, don't debug the form — dump the user's raw status value and compare it against the exact strings/enum cases the login page checks. Case sensitivity and null status are frequent culprits (e.g., users created before you added the column).

Watch the Filament major version. Filament v3 → v4 changed resource/page APIs, plugin signatures, and the schema builder namespace. Verify the package's composer.json constraint against your Filament version before installing; a mismatched major will install but break at runtime with method-not-found errors inside the resource.

Swapping the model has three touch points, not one: config/filament-user.php, config/auth.php (providers.users.model), and any hardcoded User::class in policies, factories, or seeders. Miss one and you'll get "call to undefined method" only on specific pages.

Resource discovery conflicts. If you also discoverResources() in app/Filament/Resources, a local UserResource and the package's will both register, producing duplicate nav items. Either disable discovery for that path or unregister one explicitly.

Publish tags are not guaranteed stable across releases (especially given the sparse release history). Always run the bare vendor:publish --provider=... first to list actual tags rather than trusting docs or README examples. filament-user-translations is confirmed as of 3.2.0 but could move again.

Debugging the login flow: the status check usually lives in the page's authenticate() / attempt() override. Override that method in a subclass rather than editing vendor code, and log the rejection reason server-side while keeping the client-facing error generic.

Extension points worth knowing:

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
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