riverskies/laravel-mobile-detect
Laravel wrapper around the Mobile Detect library that identifies phones, tablets, and desktops from the user agent. Provides a clean facade, Blade directives, and configurable detection so you can serve device-specific views and content with minimal boilerplate.
Install and confirm it loads
composer require riverskies/laravel-mobile-detect
On Laravel 5.5+ package discovery registers Riverskies\Laravel\MobileDetect\MobileDetectServiceProvider automatically. On older apps add the provider and the MobileDetect facade alias to config/app.php manually. There is nothing to publish or migrate — detection is driven entirely by the request's User-Agent through mobiledetect/mobiledetectlib.
Where to look first
vendor/riverskies/laravel-mobile-detect/src/MobileDetectServiceProvider.php — this is the whole package. It registers the Blade directives (@mobile, @desktop, @tablet plus their @else* / @end* counterparts) and the container binding behind the facade. Read it before assuming a directive name exists.vendor/mobiledetect/mobiledetectlib/MobileDetect.php — the real detection API (isMobile(), isTablet(), isIOS(), version('iOS'), is('Chrome')). Everything the package does is a thin wrapper over this.First use case: a device-specific include in your layout
The 10-minute win is swapping a heavy desktop nav or hero for a lighter mobile variant, without duplicating the whole layout:
{{-- resources/views/layouts/app.blade.php --}}
@mobile
@include('partials.nav-mobile')
@elsedesktop
@include('partials.nav-desktop')
@endmobile
Or, for a single conditional block:
@desktop
<div class="mega-menu">...</div>
@enddesktop
Clear compiled views once after installing (php artisan view:clear) so the new directives are picked up.
Decide in PHP, not in Blade, when logic grows
Blade directives are convenient for markup branching but get unwieldy for anything with more than two branches. Push the decision into a controller, middleware, or view composer and pass a simple value down:
use Riverskies\Laravel\MobileDetect\Facades\MobileDetect;
class HomeController extends Controller
{
public function index()
{
return view('home', [
'device' => MobileDetect::isTablet() ? 'tablet' : (MobileDetect::isMobile() ? 'mobile' : 'desktop'),
'liteMode' => MobileDetect::isMobile(),
]);
}
}
Then @if($liteMode) in the view. This keeps the view testable and makes it trivial to stub the value.
Share device class app-wide via middleware
For a project that touches device type in many views, resolve it once per request and share it:
public function handle($request, Closure $next)
{
$detect = app('mobile-detect'); // check the provider for the exact binding key
View::share('device', match (true) {
$detect->isTablet() => 'tablet',
$detect->isMobile() => 'mobile',
default => 'desktop',
});
return $next($request);
}
Caveat: resolve through the container once and reuse, rather than calling the facade in a loop of 50 product cards.
Surface the device to JavaScript and CSS
Server-side detection complements, not replaces, responsive CSS. A common pattern is to stamp the device class on <body> or <html> so client-side behaviour (lazy-load thresholds, slider config) can key off it:
<body class="@mobile is-mobile @endmobile @desktop is-desktop @enddesktop">
Or emit a meta tag / data attribute rather than branching large chunks of markup.
Feature gating rather than layout forking
The pattern that ages best: serve identical markup and use the detector to gate optional features — autoplay video, an interactive map, a heavy third-party widget, infinite scroll:
@desktop
@include('partials.map-interactive')
@elsedesktop
<img src="{{ $staticMapUrl }}" alt="Location">
@endmobile
If you find yourself maintaining two entirely different page trees, stop and use responsive CSS instead.
Redirects and subdomains
In middleware, before the response is generated:
if (MobileDetect::isMobile() && ! $request->is('m/*') && ! $request->isMethod('post')) {
return redirect()->to('https://m.example.com' . $request->getRequestUri());
}
Guard against POST requests and preserve query strings, otherwise form submissions die.
Testing
Set the user agent explicitly on the request and assert on the rendered output:
$response = $this->withHeader('User-Agent', 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15')
->get('/');
$response->assertSee('nav-mobile');
$response->assertDontSee('mega-menu');
If the header override appears to be ignored, the underlying detector has cached or read $_SERVER['HTTP_USER_AGENT'] directly — set that in the test as a fallback and reset it in tearDown(). For controller-level tests, bind a fake detector into the container rather than simulating a UA.
It is user-agent sniffing, and user agents lie
MobileDetect parses a string the client controls. Treat the result as a presentation hint, never as trust or authorization. Edge cases that bite in production:
isTablet() returns false for iPads set to "Request Desktop Website". Modern Android tablets are also inconsistent.Caching will betray you
If you branch HTML on device and then put it behind a CDN, a page cache, or Cache::remember, desktop users will get the mobile markup or vice versa. Either vary the cache key by device class, send Vary: User-Agent, or (preferably) keep device-specific markup out of cached pages and do it client-side. This is the single most common way this package causes a "random broken layout" bug.
Blade directive nesting and @else
The package's branches compile to raw if/else PHP. Do not mix a bare @else inside a @mobile block and expect it to bind to an outer @if — use the package's own @elsemobile/@elsedesktop/@endmobile pairs, and verify the exact set of directive names in the service provider for your installed version. Also, after upgrading the package, run php artisan view:clear; stale compiled views can keep referencing directives that no longer exist.
No HTTP request, no meaningful answer
In console commands, queue workers, and some tests there is no request UA — the detector falls back to the PHP process's $_SERVER, which typically yields "desktop". Never let a queued job's behaviour depend on isMobile(); pass the device decision into the job as data.
Package age is the real constraint
Last release was 2017, so the Composer constraints (illuminate/support 5.x) will block installation on Laravel 9/10/11. Options, in order of preference:
mobiledetect/mobiledetectlib is still maintained; bind it in a tiny service provider and register your own @mobile directives with Blade::if('mobile', fn () => app(MobileDetect::class)->isMobile()). Laravel's Blade::if gives you real @mobile ... @elsemobile ... @endmobile semantics for free.jenssegers/agent for a richer, maintained wrapper over the same library.Performance and extension points
UA parsing is a regex pass over a short string — cheap, but resolve the detector once per request (container singleton or a single facade call) rather than inside loops over collections. To reach capabilities the package doesn't expose, resolve the underlying MobileDetect instance and call it directly — isIOS(), version('Android'), is('Chrome') — or decorate the binding in your own service provider with a class that adds methods like isOldAndroid(); the container binding is the extension point, so you can swap it wholesale in tests.
Don't build a separate site on this
The maintenance cost of two full view trees is high and the SEO risk is real. Use detection for progressive enhancement, redirects, and light feature gating; use CSS media queries for layout.
How can I help you explore Laravel packages today?