widiu7omo/filament-bandel
Filament Bandel is a Laravel package that integrates Bandel functionality into Filament admin panels. It provides a simple, configurable way to manage Bandel records and workflows alongside your existing Filament resources, with native UI and easy installation.
This is a Filament plugin that wraps cybercog/laravel-ban in ready-made table/page actions, so most of your setup is the underlying ban package plus one line per action.
composer require widiu7omo/filament-bandelBannable trait, publish/run the ban package's migrations so the banned_at column (and ban reason, if you use it) exists, then php artisan migrate.use Widiu7omo\FilamentBandel\Actions\BanAction;
use Widiu7omo\FilamentBandel\Actions\UnbanAction;
public static function table(Table $table): Table
{
return $table->actions([
BanAction::make(),
UnbanAction::make(),
]);
}
src/Support/FilamentVersion.php (how the four actions resolve to Filament\Tables\Actions\* on v3 vs Filament\Actions\* on v4/v5), src/Forms/BanForm.php (the shared single/bulk ban schema you'll be customising), and the Pest suite — it's now real testbench coverage with per-major construction tests, which is the fastest way to see what a correctly-configured action looks like per Filament version.Single vs bulk are the same form. Both ban actions render Forms\BanForm, so whichever surface you attach (->actions() for rows, ->bulkActions() for selection, or a header action on a record page), the fields and validation are identical. Customise once by chaining on the instance and letting both paths inherit — don't duplicate field definitions.
Customise by chaining, never by extending. All action classes are final. The supported pattern is composition:
BanAction::make()
->label('Suspend account')
->visible(fn (User $record) => auth()->user()->can('ban', $record))
->after(fn (User $record) => activity()->on($record)->log('banned'));
Anything you previously did in a subclass (protected function setUp(), overridden getFormSchema()) now has to be an -> call on the returned instance. If you need a named, reusable variant, wrap it in your own factory function that returns the configured instance.
Use bulk actions for moderation queues. The bulk ban/unban paths re-read record state via Support\BulkRecords::fresh() before filtering and run inside a transaction, so a partial failure on a 200-row selection rolls back cleanly instead of half-banning. If you write your own bulk ban action (e.g. "ban all reported users"), route it through BulkRecords::fresh() too — you'll hit the same stale-model trap they fixed.
Filter and display off banned_at. Pair the actions with an IconColumn/ToggleColumn on banned_at and a ternary filter so moderators can see and slice banned users without a custom query.
Keep side effects out of the action itself. laravel-ban fires model events; if you're emailing the user or hitting an external API, do it in a listener with ShouldQueue so the bulk transaction isn't held open by SMTP.
Test the way the package does. SQLite + orchestra/testbench, asserting banned_at after invoking the action. Because action construction varies per Filament major, add one test that just asserts the action instantiates on your installed version — that's the failure mode that bites silently.
make() no longer takes a custom name. BanAction::make('banUser') breaks. Use the actions array key (['banUser' => BanAction::make()]) or ->label() for the human-facing name.final means the old "extend and override setUp" recipe is dead. Anything not exposed as a fluent method is now a fork.->form([...]); on v4/v5 it's ->schema([...]). If you customise the ban form, either write version-agnostic code or branch on Support\FilamentVersion — otherwise your customisation silently no-ops on upgrade.laravel-ban observers set banned_at on separate instances, so the $record in your ->after() closure (or in a table row you re-render) can be out of date. $record->refresh() before reading ban state, and refresh the table after the action.expired_at cannot be in the past. Good validation, but it means the form is for creating bans only — you can't use it to backdate or to edit an existing ban's expiry. Do that programmatically.composer show filament/filament and read FilamentVersion — the Tables\Actions vs Actions namespace split is the usual culprit after a Filament upgrade.How can I help you explore Laravel packages today?