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

Banhammer Laravel Package

mchev/banhammer

Laravel package for banning users, IP addresses, and other entities with a polymorphic ban model, middleware, events, and helper methods. Supports temporary or permanent bans, reasons, and easy integration into existing applications.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with composer require mchev/banhammer, then publish and run the migrations (php artisan vendor:publish → provider/tag, php artisan migrate). The package ships its own bans table; review the migration before running it in production.
  • Add the Bannable trait to any model you want to be bannable — in most apps that's App\Models\User, but it works on any Eloquent model.
  • Register the blocking middleware. On Laravel 11+/13 that's bootstrap/app.php (->withMiddleware(...)); on older skeletons it's app/Http/Kernel.php. Put it early in the global or web stack so banned IPs are rejected before session/auth work happens.
  • Skim config/banhammer.php first — that's where cache behaviour, table naming, and the middleware/route toggles live. It's the single best map of the package's surface area.
  • Confirm the artisan surface with php artisan list | grep -i ban rather than trusting docs; the ban/unban/list commands are the fastest way to validate your install end-to-end.
  • First use case: ban an abusive IP. From tinker, ban the address, hit the app from that IP, and confirm you get the package's rejection response instead of your normal page. Once that works, move on to banning a user model.

On v2.5.0: this release is purely compatibility — Laravel 13.x support plus a PHP 8.5 test matrix entry. If you're on Laravel 11/12 and PHP 8.2–8.4, nothing changes for you. It does mean the package is safe to carry forward through a Laravel 13 upgrade, which is worth noting if you're planning one.

Implementation Patterns

Model-level bans (the common case). With the trait on User, you get a fluent API for banning, unbanning, and checking status. The usual shape is:

$user->ban(['reason' => 'spam', 'expired_at' => now()->addDays(7)]);
$user->unban();
$user->isBanned();

Wrap this in a small BanService rather than calling it from controllers — you'll want one place to dispatch audit events, notify moderators, and write to your own log table.

IP bans for unauthenticated traffic. The middleware is what actually enforces bans on guests, since there's no user to check. Keep a separate admin action for "ban this IP" that captures the request IP plus a reason, so you can trace why an address was blocked six months later.

Temporary bans and expiry. Prefer setting an expiry over manual unbanning — it makes support tickets self-resolving. Whatever you do, don't assume expired rows vanish on their own: verify whether enforcement is a query scope or a cleanup job, and if it's the latter, schedule it (Schedule::command(...)->hourly() or a queued job). Add a test that a ban with a past expired_at no longer blocks access.

Country / geo-based bans. If you use country banning, it depends on a GeoIP lookup driver and a local database file. Treat this as an optional layer: it adds a dependency, a data file you must keep updated, and latency on the request path. Cache the country resolution per IP if you enable it on high-traffic routes.

Moderation UI integration. A typical pattern is a BansController with store/destroy, a BanPolicy, and a Banned / Unbanned event listener that writes to an audit_logs table and pings Slack. The package's events are the intended extension point here — listen rather than subclass.

Auth integration. Pair bans with login throttling rather than replacing it. A banned user should get a generic rejection (don't leak "you are banned" to an attacker probing accounts); a banned IP should get a flat 403 from middleware.

Testing. Use RefreshDatabase and be explicit about IPs: $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.10']) for middleware tests, and $this->withHeaders([...]) if you're behind a trusted proxy setup. Add a banned() factory state for convenience. Flush the ban cache in setUp() — cached ban lists leaking between tests is the single most common source of flaky suites with this package.

Gotchas and Tips

  • Middleware order matters more than anything else. Register the ban check before StartSession, Authenticate, and anything that writes to the DB. A banned IP should cost you one cache read, not a full session bootstrap.
  • Proxies and load balancers silently break IP bans. If you're behind Cloudflare/ALB/nginx, REMOTE_ADDR is the proxy. You must have TrustProxies configured with the correct ranges, or every ban will match the wrong address (or nothing). Test from a real client IP, not just localhost.
  • 127.0.0.1 and ::1 are not the same ban. IPv4 vs IPv6 mismatches are a frequent "the ban doesn't work" bug report. Ban both forms, and check whether CIDR ranges are supported before promising subnet bans to anyone.
  • Caching is the debugging trap. Ban lists are cached, so direct DB inserts (seeders, fixtures, manual SQL) won't take effect until the cache clears. Always go through the package API in code, and know the cache key/tag so you can bust it in an incident.
  • Don't edit the published migration after it's run. Table name is configurable; pick one and keep it consistent across local/staging/prod, or you'll get "table not found" on deploy.
  • Expired bans: verify, don't assume. Read the package source or write a test to confirm whether enforcement respects expired_at. If cleanup is a separate concern, schedule it yourself and monitor it.
  • GeoIP adds operational weight. The country database needs periodic refresh and is a no-op for private IP ranges (so it will never work in local dev). Don't build your primary abuse defence on it.
  • Extension points to lean on: events (for audit/notifications), the config file (table name, cache), and container binding if you need to swap the Ban model. Avoid forking the package for behaviour you can get from a listener.
  • Upgrade guidance for v2.5.0: no breaking changes from 2.4.x — it's a Laravel 13 + PHP 8.5 compatibility release. Bump it freely, but if you're pinned to an older Laravel or PHP, check the composer constraints before running composer update, since the new constraint set may pull you forward. Run your middleware tests after upgrading to confirm proxy/IP behaviour is unchanged.
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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource