spinen/laravel-browser-filter
Laravel middleware that blocks requests from specified browsers and devices using browser-filter rules. Define allowed or denied user agents in config and redirect or reject unsupported visitors before they reach your routes.
Install the package and confirm which Laravel version it supports before wiring anything into production:
composer require spinen/laravel-browser-filter
This is a Laravel 5.x-era package (last release April 2018), so on modern Laravel you may need to register the service provider manually in config/app.php rather than relying on package discovery:
'providers' => [
// ...
Spinen\BrowserFilter\BrowserFilterServiceProvider::class,
],
Publish the config and views so you can see the actual rule keys the package expects:
php artisan vendor:publish --provider="Spinen\BrowserFilter\BrowserFilterServiceProvider"
Register the middleware alias in app/Http/Kernel.php:
protected $routeMiddleware = [
// ...
'browser.filter' => \Spinen\BrowserFilter\Middleware\BrowserFilter::class,
];
Then apply it to the smallest surface that needs it. The first practical use case is gating a flow that genuinely breaks on old browsers — a checkout, an uploader, a dashboard using modern JS:
// routes/web.php
Route::middleware('browser.filter')->group(function () {
Route::get('/checkout', 'CheckoutController@show');
});
Before going further, read the published config file and the middleware class itself (vendor/spinen/laravel-browser-filter/src/). The important things to verify are: the browser-name/version rule format, what happens when the user agent can't be identified (fail open or fail closed), and whether the rejection is a redirect or a view.
A minimal config will look roughly like a map of browser names to minimum versions, plus a rejection target:
// config/browser-filter.php (verify exact keys against the package)
return [
'browsers' => [
'chrome' => 50,
'firefox' => 45,
'edge' => 12,
'ie' => 11,
'safari' => 9,
],
'redirect' => '/browser-unsupported',
];
Tier your enforcement. Don't apply the middleware globally. Use a hard block for critical flows and a soft banner for marketing pages, because a hard redirect on the homepage will hurt conversion and SEO.
// Hard block: only where the app actually breaks
Route::middleware('browser.filter')->group([
'/checkout',
'/account/security',
'/admin',
], function () { /* ... */ });
Exclude the rejection route from the filter. If your unsupported page lives behind the same middleware group, you get a redirect loop. Keep it in the web group only.
Handle JSON/XHR separately. A redirect response to an AJAX call is confusing. Either create a second middleware alias that returns 426 Upgrade Required with a JSON payload, or subclass the middleware and branch on $request->expectsJson():
if ($request->expectsJson()) {
return response()->json([
'error' => 'unsupported_browser',
'message' => 'Please update your browser to continue.',
], 426);
}
Use per-route parameters for narrower rules. If the middleware accepts parameters, prefer browser.filter:chrome,firefox,edge on routes where only a subset is supported, rather than editing global config for one page.
Give users an escape hatch. Store an "I understand" cookie and have a thin wrapper middleware skip the check when it's present. This avoids trapping users whose browser is misdetected.
if ($request->cookie('browser_ack')) {
return $next($request);
}
Build a test around the user agent. This is the cheapest way to catch regressions when you upgrade the package or the config:
public function test_old_ie_cannot_reach_checkout()
{
$this->withHeader('User-Agent', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)')
->get('/checkout')
->assertRedirect('/browser-unsupported');
}
public function test_modern_chrome_can_reach_checkout()
{
$this->withHeader('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36')
->get('/checkout')
->assertOk();
}
Log rejections. Add a small override or a listener that logs the blocked user agent. You'll want this data to justify the blocklist and to spot false positives from mobile WebViews.
Keep feature detection alongside it. The middleware is a coarse gate; use @supports in CSS and runtime checks in JS for capabilities. Don't let the PHP middleware become the only thing protecting a JS-only feature.
php artisan route:list -v that the alias is actually attached where you think it is.php artisan config:clear (and config:cache in deploy). Rules that work in local often silently don't apply in production because cached config was built before you published the file.expectsJson() or use a separate alias returning 426.hisorange/browser-detect or jaybizzle/crawler-detect directly with a 20-line custom middleware. The wrapper's value is the config-driven rule set, not the detection itself.browser-filter::.... Publish views (vendor:publish --tag=views or the provider tag) before customizing, or you'll be editing inside vendor/.Log::info($request->userAgent()) at the top of the middleware, confirm the UA string, then test the detector directly in Tinker. Nine times out of ten it's a browser name mismatch (ie vs internet explorer, edge vs edg) rather than the version comparison.How can I help you explore Laravel packages today?