reefki/laravel-device-detector
Laravel integration for device detection that parses user agents to identify device type (mobile, tablet, desktop), OS, browser, and bots. Access results in Blade views, middleware, or anywhere in your app to serve tailored content, redirect traffic, and improve analytics.
Install and publish
composer require reefki/laravel-device-detector
php artisan vendor:publish --tag=device-detector-config # verify tag name in the provider
The service provider is auto-discovered; you should get a config/device-detector.php and a facade (typically Reefki\DeviceDetector\Facades\DeviceDetector). Read that config file first — it is where caching, bot handling, and any UA-version truncation options live.
First use case: branch the response on device type
The 80% use case is "serve the mobile layout / block scrapers / tag analytics" — a single parse per request:
use Reefki\DeviceDetector\Facades\DeviceDetector;
$ua = request()->userAgent();
$device = DeviceDetector::parse($ua); // wrapper handles Matomo's setUserAgent + parse
if ($device->isBot()) {
// don't count it, don't personalize for it
} elseif ($device->isMobile()) {
return view('home.mobile');
}
return view('home.desktop');
The object you get back is (or proxies) Matomo's DeviceDetector, so the day-to-day getters are:
$device->getDeviceName(); // 'smartphone', 'tablet', 'desktop', ...
$device->getBrandName(); // 'Apple', 'Samsung', ...
$device->getModel(); // 'iPhone', may be empty
$device->getOs(); // ['name' => 'iOS', 'short_name' => 'iOS', 'version' => '17.4', ...]
$device->getClient(); // ['name' => 'Mobile Safari', 'short_name' => 'MF', 'version' => ..., 'engine' => ...]
$device->isTablet(); $device->isDesktop(); $device->isTouchEnabled();
Where to look next: Matomo's device-detector README. This wrapper is thin — most of your questions are answered by the upstream library, not the package.
1. Middleware that parses once per request
UA parsing is regex-heavy; never parse twice. Resolve it once and stash it:
// app/Http/Middleware/DetectDevice.php
public function handle(Request $request, Closure $next)
{
$device = DeviceDetector::parse($request->userAgent());
$request->attributes->set('device', $device);
View::share('isMobile', $device->isMobile());
View::share('deviceType', $device->getDeviceName());
return $next($request);
}
Then in any controller: $request->attributes->get('device'), and in Blade: @if($isMobile).
2. Request macro for readable controllers
// AppServiceProvider::boot()
Request::macro('device', fn () => $this->attributes->get('device'));
Request::macro('isMobileRequest', fn () => $this->device()?->isMobile() ?? false);
3. Cache by User-Agent string
If a request can hit the detector on multiple code paths (middleware + analytics job + Blade component), key a cache on a hash of the UA:
$key = 'ua:' . md5($ua);
$payload = Cache::remember($key, now()->addDay(), function () use ($ua) {
$d = DeviceDetector::parse($ua);
return [
'type' => $d->getDeviceName(),
'brand' => $d->getBrandName(),
'model' => $d->getModel(),
'os' => $d->getOs(),
'client' => $d->getClient(),
'is_bot' => $d->isBot(),
];
});
Cache the serializable array, not the detector object — regex/rule state doesn't serialize cleanly and you don't want it in Redis.
4. Persist device context on models
For logins, orders, and support tickets, store a small schema rather than a blob:
$table->string('device_type')->nullable();
$table->string('device_brand')->nullable();
$table->string('device_model')->nullable();
$table->string('os_name')->nullable();
$table->string('os_version')->nullable();
$table->string('client_name')->nullable();
This lets you query "how many iOS 17 users hit the checkout bug?" — far more useful than a raw UA column.
5. Analytics and bot hygiene
Use isBot() + getBot() to filter traffic in your own dashboards, or to serve a cheaper response to crawlers. This is also the correct place to not run personalization: bots should get the desktop, non-personalized variant.
6. Feature tests
Don't guess from a real browser; set the header explicitly:
$this->withHeader('User-Agent', 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) ...')
->get('/')
->assertViewIs('home.mobile');
Keep a small fixture list: iPhone, iPad, Android phone, desktop Chrome, Googlebot, empty UA.
7. Queues, commands, and APIs
There is no request context in a job. Always pass the UA string into the job and call parse($ua) explicitly — don't rely on the facade reading request().
parse() before any getter, or you get unknown/empty results. Confirm whether your facade version auto-parses; if you're ever binding the underlying class yourself, this is the #1 source of "it returns nothing" bugs.getOs()['version'] can be ''; getClient()['name'] is the long form, short_name is what you usually want for compact UI. Guard with ?? null.getModel() is frequently empty. Matomo's model database is broad but not exhaustive; always fall back to brand → device type → "unknown". Don't render "Your null" in an email.config/device-detector.php in production, run php artisan config:cache. If detection behavior "changes after deploy," this is usually why.isBot() catches declared crawlers; a scraper with a fake Chrome UA will look like Chrome. Combine with rate limiting if abuse is the concern.addClientParser, prependDeviceParser etc.). If you have an embedded webview or an internal app with a custom UA, register a parser rather than string-matching str_contains($ua, ...) in your controllers.composer.json, and be prepared to call the underlying matomo/device-detector directly if the wrapper lags a Laravel major.php artisan tinker with the same string and compare. If both agree, the UA is genuinely ambiguous, not a wrapper bug.How can I help you explore Laravel packages today?