Courses

Filament Adminpanel for Booking.com API Project

Users List and Filter by Role

The first page and Filament Resource in this course will be for the list of users. Also, we will add a filter based on the user role.

We will also add a role restriction: all the actions like creating new administrator users, changing passwords, and deactivating users will be accessible only for users with the "administrator" role.

new actions


Adding User Soft Deletes

When the user is deactivated, they will be deleted using Soft Deletes. So first, let's add softDeletes to the User model.

php artisan make:migration "add soft deletes to users table"

database/migrations/xxx_add_soft_deletes_to_users_table:

public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->softDeletes();
});
}

app/Models/User.php:

use Illuminate\Database\Eloquent\SoftDeletes;
 
class User extends Authenticatable implements FilamentUser
{
use SoftDeletes;
 
// ...
}

Filament User Table and Form

Now let's move on to the Filament part. First, we need to create a Resource.

php artisan make:filament-resource User

We won't be allowing to edit users, so you can delete the app\Filament\Resources\UserResource\Pages\EditUser.php file. Then we also need to remove the page from the UserResource.

app/Filament/Resources/UserResource.php:

 
class UserResource extends Resource
{
// ...
 
public static function getPages(): array
{
return [
'index' => Pages\ListUsers::route('/'),
'create' => Pages\CreateUser::route('/create'),
'edit' => Pages\EditUser::route('/{record}/edit'),
];
}
}

Now let's add a Filament Form.

app/Filament/Resources/UserResource.php:

use Illuminate\Validation\Rules\Password;
 
class UserResource extends Resource
{
protected static ?string $model = User::class;
 
protected static ?string $navigationIcon = 'heroicon-o-user';
 
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->required(),
Forms\Components\TextInput::make('email')
->email()
->required()
->unique(),
Forms\Components\TextInput::make('password')
->required()
->password()
->maxLength(255)
->rule(Password::default())
]);
}
// ...
}

This will create a form like the one below:

create user form


Mutate User Data Before Saving

Now that we have a form, we need to do two things when creating a new user:

  1. Hash the password before saving it to the DB.
  2. Set the role to the admin because we will only manage administrators from the admin panel.

To do this, we will add a mutateFormDataBeforeCreate() method in the CreateUser file. Also, we will a little info text that this form will create an administrator user.

app/Filament/Resources/UserResource/Pages/CreateUser.php:

use App\Models\Role;
use Illuminate\Support\Facades\Hash;
use Illuminate\Contracts\Support\Htmlable;
 
class CreateUser extends CreateRecord
{
protected static string $resource = UserResource::class;
 
protected function getSubheading(): string|Htmlable|null
{
return 'This form will create an administrator user';
}
 
protected function mutateFormDataBeforeCreate(array $data): array
{
$data['password'] = Hash::make($data['password']);
$data['role_id'] = Role::ROLE_ADMINISTRATOR;
 
return $data;
}
}

Table Columns and Sorting

Next, let's add columns to the Users table and make the default sort by created_at field, descending.

app/Filament/Resources/UserResource.php:

class UserResource extends Resource
{
// ...
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('role.name'),
Tables\Columns\TextColumn::make('created_at')
->dateTime(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
])
->defaultSort('created_at', 'desc');
}
// ...
}

Now we have a table.

users list table


Actions: Change Password and Deactivate User

Now let's add two actions: instead of the default edit action, we need actions for changing the password and deactivating the user.

These two actions will be visible only if the user role is Administrator.

app/Filament/Resources/UserResource.php:

use App\Models\Role;
use Filament\Tables\Actions\Action;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
 
class UserResource extends Resource
{
// ...
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('role.name'),
Tables\Columns\TextColumn::make('created_at')
->dateTime(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Action::make('changePassword')
->action(function (User $record, array $data): void {
$record->update([
'password' => Hash::make($data['new_password']),
]);
 
Filament::notify('success', 'Password changed successfully.');
})
->form([
Forms\Components\TextInput::make('new_password')
->password()
->label('New Password')
->required()
->rule(Password::default()),
Forms\Components\TextInput::make('new_password_confirmation')
->password()
->label('Confirm New Password')
->rule('required', fn($get) => ! ! $get('new_password'))
->same('new_password'),
])
->icon('heroicon-o-key')
->visible(fn (User $record): bool => $record->role_id === Role::ROLE_ADMINISTRATOR),
Action::make('deactivate')
->color('danger')
->icon('heroicon-o-trash')
->action(fn (User $record) => $record->delete())
->visible(fn (User $record): bool => $record->role_id === Role::ROLE_ADMINISTRATOR),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
])
->defaultSort('created_at', 'desc');
}
// ...
}

Now we have two actions:

new actions

The Deactivate action will soft-delete the user, and the Change password will open a modal to change the user's password.

change password modal


Table Filter for Roles

Last thing for this table: let's add a filter for the user role.

app/Filament/Resources/UserResource.php:

class UserResource extends Resource
{
// ...
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('role.name'),
Tables\Columns\TextColumn::make('created_at')
->dateTime(),
])
->filters([
Tables\Filters\SelectFilter::make('role')
->options([
Role::ROLE_USER => 'User',
Role::ROLE_OWNER => 'Owner',
Role::ROLE_ADMINISTRATOR => 'Administrator',
])
->attribute('role_id'),
])
->actions([
Action::make('changePassword')
->action(function (User $record, array $data): void {
$record->update([
'password' => Hash::make($data['new_password']),
]);
 
Filament::notify('success', 'Password changed successfully.');
})
->form([
Forms\Components\TextInput::make('new_password')
->password()
->label('New Password')
->required()
->rule(Password::default()),
Forms\Components\TextInput::make('new_password_confirmation')
->password()
->label('Confirm New Password')
->rule('required', fn($get) => ! ! $get('new_password'))
->same('new_password'),
])
->icon('heroicon-o-key')
->visible(fn (User $record): bool => $record->role_id === Role::ROLE_ADMINISTRATOR),
Action::make('deactivate')
->color('danger')
->icon('heroicon-o-trash')
->action(fn (User $record) => $record->delete())
->visible(fn (User $record): bool => $record->role_id === Role::ROLE_ADMINISTRATOR),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
])
->defaultSort('created_at', 'desc');
}
// ...
}

This will add a select filter with the three roles as an option.

role filter

avatar

getSubheading() does not match \vendor\filament\filament\src\Resources\Pages\CreateRecord.php there is a getTitle() ??

avatar

This is the link to the exact place in the source.

avatar

app/Filament/Resources/UserResource/Pages/CreateUser.php

protected ?string $subheading = 'This form will create an administrator user';
avatar

There is a better option for the filter, if there is a realtionship. Instead of manually doing this

				->filters([
            Tables\Filters\SelectFilter::make('role')
            ->options([
                Role::ROLE_USER => 'User',
                Role::ROLE_OWNER => 'Owner',
                Role::ROLE_ADMINISTRATOR => 'Administrator',
            ])
            ->attribute('role_id'),
        ])
					
//We can use the relation as below

 ->filters([
            Tables\Filters\SelectFilter::make('role')->relationship('role', 'name')
        ])
					
👍 4
avatar

Allow admin to change all passwords:

Action::make('changePassword')
  ...
  ->visible(fn (User $record): bool => auth()->user()->role_id === Role::ROLE_ADMINISTRATOR),

And deactivate other users only:

Action::make('deactivate')
  ...
	->visible(fn (User $record): bool => auth()->user()->role_id === Role::ROLE_ADMINISTRATOR && $record->id !== auth()->id()),
avatar

I can't find anything about Filament::notify() to resolve this error here:

production.ERROR: Call to undefined method Filament\FilamentManager::notify() {"userId":1,"exception":"[object] (Error(code: 0): Call to undefined method Filament\\FilamentManager::notify() at vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:358)
[stacktrace]
#0 app/Filament/Resources/UserResource.php(116): Illuminate\\Support\\Facades\\Facade::__callStatic('notify', Array)
...
avatar

Because here it uses the old syntax. Check the docs for notification and you will