gerenuk/filament-banhammer
Filament Banhammer brings ban management to Filament admin panels, letting you ban and unban users, schedule bans, and block banned accounts from accessing your app. Manage moderation directly from Filament with dedicated resources and controls.
^3.2 to ^5.7. This is a hard breaking change — you cannot install v2 on a Filament v3 or v4 panel. If you're still on v3/v4, stay on v1.2.0 and pin it; if you're on v5, use ^2.0. Either way, pin an explicit version in composer.json — this is still a very low-adoption package (~12 stars, 0 dependents).composer require gerenuk/filament-banhammer:^2.0 (see version note above).php artisan vendor:publish --tag="filament-banhammer-migrations" then php artisan migrate. Read the migration before running it — the v2 feature set (IP/country blocking) likely means additional tables or columns beyond the original banned_at / banned_until / ban_reason fields. Note which table holds IP and country entries.App\Models\User. Look in src/ for the trait name and confirm the helpers it exposes (isBanned(), banned() scope, and anything new for IP/country checks).table()->actions([...]) and the bulk action in table()->bulkActions([...]) on the new v5 API. Add the provided column and filter if shipped.Look first at the migration (what columns/tables exist), the trait (what methods exist), the Filament action classes (form fields rendered), and the new authorization/IP/country classes. Those define the usable API.
Row + bulk actions on the users table. Add the ban action as both a row action and a bulk action so admins can ban one or many users from the list view. Pair with the unban action so the same table supports reversal without editing the record.
Header action on the edit page. Add to getHeaderActions() so an admin viewing a specific user can ban/unban in context, with the reason field visible next to the profile data.
Status column and filter. Add the package's column for at-a-glance state, and a ternary filter (banned / not banned). If the package doesn't ship one, a TextColumn::make('banned_at')->since() plus a Filter on whereNotNull('banned_at') is trivial.
Model-level enforcement is still your job. Filament actions only change data. Unless v2's authorization layer covers your surfaces, enforce in:
canAccessPanel() on the user model, returning false when banned.Login event listener (or login controller check) that rejects banned users.Also consider the new authorization hook the release notes mention — check src/ for how it's meant to plug into your app, since that's likely the intended enforcement point.
IP blocking. v2 adds IP blocking, which is a different axis from user bans: it doesn't care who the user is. Decide where it's enforced — typically a global middleware ($middleware->prepend(...) in bootstrap/app.php on Laravel 11+) so it runs before auth. Confirm whether the package ships the middleware or just stores the IP records; the release notes are ambiguous. Be careful with trustedProxies — if you read the wrong IP behind a load balancer, you'll block your own proxy.
Country blocking. Same pattern as IP: storage plus an enforcement point. You need a geo-IP source to resolve country (the package may or may not bundle one — check dependencies). Decide whether blocking is "deny listed countries" or "allow only listed countries"; getting that inverted locks out everyone. Consider the privacy/GDPR implications of logging geolocation against users.
Time-limited bans. If banned_until is supported, either check expiry at access time (banned_at && (banned_until === null || banned_until->isFuture())) or schedule a command to clear expired bans. Check-at-access avoids cron drift; scheduled clearing is better if downstream systems read the column directly.
Reuse on other models. The trait is model-agnostic — apply it to Comment, Review, or Organization if you want the same ban UI for non-user records. Publish a second migration or copy the columns.
Notifications and audit. Listen for the model events the trait fires (or override the action's after() callback) to email the banned user, notify moderators, or write to an audit log. This is where the reason field feeds your internal admin notes.
Testing. Use the trait in factories (User::factory()->banned() if a state is provided) and assert canAccessPanel() returns false. Filament action tests via livewire()->callTableAction() confirm the wiring. Add dedicated tests for IP and country blocks — they're the least familiar paths and the easiest to get wrong.
^3.2 to ^5.7. Upgrading a v3 or v4 panel is not possible without first upgrading Filament. Plan the Filament upgrade as the real project; the banhammer bump is a footnote on top of it.src/ and the migration before assuming method names, column names, or behavior. Pin the version and read the v1.2.0→v2.0.0 diff for anything the release notes omitted.Auth::logoutOtherDevices, session invalidation) or enforce per-request middleware. Sanctum/API tokens are not revoked by a banned_at column — add tokens()->delete() to your ban flow if you expose an API.withoutGlobalScope(...) in getEloquentQuery() or don't enable the scope.TrustProxies is configured before deploying IP blocking; resolving to your load balancer's IP bans everyone or no one depending on the direction of the check.banned_at / banned_until may already exist from another moderation package. Rename via the published migration before running it and update the trait's expectations.banned_until. Cast to datetime in $casts and store UTC. Comparing local-time against now() is a classic "ban expired early/late" bug.handle() methods with an isBanned() check.users, check timestamp ordering; column-order assumptions in seeders can bite.authorize() on the action and the resource policy — Filament silently hides or no-ops actions the current user isn't authorized for. With the new authorization feature, also confirm you've granted the ban ability to the roles that need it, or admins will lose access to controls they had before.How can I help you explore Laravel packages today?