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

Mobiledetectlib Laravel Package

mobiledetect/mobiledetectlib

MobileDetect is a lightweight PHP class that detects mobile devices and tablets using the User-Agent string plus specific HTTP headers. It requires no dependencies beyond PHP, supports all major frameworks, and ships with a large, regularly updated device database.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the current line, then check your PHP floor first. MobileDetect 4.x is the only branch you should be adding to a new project (3.x is LTS for PHP 7.4, 2.x is deprecated and uses the old \Mobile_Detect class name). Note that 4.10.0+ requires PHP >= 8.2 — a Laravel 10 app pinned to PHP 8.1 must stay on 4.9.x or drop to 3.x.

composer require mobiledetect/mobiledetectlib:^4.10

Where to look first

  1. Detection\MobileDetect — the whole public surface you need day-to-day: isMobile(), isTablet(), isPhone()-style composition, is('iOS'|'Android'|'iPhone'|'Chrome'), version('iOS').
  2. The docs at docs.mobiledetect.net (Gitbook) for the full method list — the README is intentionally thin.
  3. changelog/ in the repo if you are upgrading across 4.8.x → 4.9.x → 4.10/4.11; the BC notes there are the ones that bite (see Gotchas).

First use case: classify once per request, then branch in Blade. The simplest thing that pays for itself is serving a different hero/CTA or redirecting to an m-dot host:

use Detection\MobileDetect;

$detect = new MobileDetect();          // autoInitOfHttpHeaders = true by default
$isTablet = $detect->isTablet();
$isPhone  = $detect->isMobile() && !$isTablet;   // isMobile() includes tablets
$isIOS    = $detect->is('iOS');
$iosMajor = $detect->version('iOS');             // float by default

Then return view('home', compact('isPhone', 'isTablet', 'isIOS'));.

The key mental model: this is a UX hint, not a security control. Never use it for authorization, rate limiting, or anything an attacker can spoof with a header.

Implementation Patterns

Bind it as a scoped singleton and inject the UA explicitly. Do not let the class sniff $_SERVER in long-running workers (see Gotchas). Wrap it in a tiny service so the UA is set exactly once, from the current request:

namespace App\Support;

use Detection\MobileDetect;
use Illuminate\Http\Request;

final class DeviceDetector
{
    private MobileDetect $detect;

    public function __construct(Request $request)
    {
        $this->detect = new MobileDetect();
        $this->detect->setUserAgent($request->userAgent() ?? '');
    }

    public function isPhone(): bool   { return $this->detect->isMobile() && ! $this->detect->isTablet(); }
    public function isTablet(): bool  { return $this->detect->isTablet(); }
    public function isDesktop(): bool { return ! $this->detect->isMobile(); }
    public function is(string $k): bool { return $this->detect->is($k); }
    public function version(string $k = 'iOS', int $type = MobileDetect::VERSION_TYPE_FLOAT) { return $this->detect->version($k, $type); }
}
// AppServiceProvider::register()
$this->app->scoped(DeviceDetector::class, fn ($app) => new DeviceDetector($app['request']));

scoped (not singleton) is what keeps it correct under Octane — a singleton would leak the first request's UA into every subsequent request on that worker.

Middleware that classifies once and shares with views. Run it in web, stash the result on the request and share a single boolean bundle to Blade so no template ever constructs MobileDetect itself:

public function handle(Request $request, Closure $next)
{
    $device = app(DeviceDetector::class);
    $request->attributes->set('device', [
        'phone'    => $device->isPhone(),
        'tablet'   => $device->isTablet(),
        'desktop'  => $device->isDesktop(),
    ]);
    View::share('device', $request->attributes->get('device'));

    return $next($request);
}

Blade directives make templates readable and keep the detection out of view code:

// AppServiceProvider::boot()
Blade::if('phone',   fn () => request()->attributes->get('device')['phone'] ?? false);
Blade::if('tablet',  fn () => request()->attributes->get('device')['tablet'] ?? false);
Blade::if('desktop', fn () => request()->attributes->get('device')['desktop'] ?? false);
@phone
    <a href="{{ $appStoreUrl }}" class="btn-primary">Open in the app</a>
@elsedesktop
    <a href="{{ $downloadUrl }}">Get the app</a>
@endphone

Routing-level branching. For an m-dot or a stripped-down mobile route group, a small middleware with an allow-list is more maintainable than sprinkling conditionals in controllers:

public function handle($request, Closure $next)
{
    if (app(DeviceDetector::class)->isDesktop() || $request->is('m/*')) {
        return $next($request);
    }
    return redirect()->route('m.home');   // keep a `?full=1` escape hatch
}

Always ship the escape hatch (a query param or cookie that opts out of the redirect) — tablet UA classification around iPadOS is good but not perfect, and users who land on the wrong experience blame the site, not the regex.

Response shaping and assets. Cheap, high-value uses that don't need a redirect: smaller pagination per_page, WebP only for isMobile() clients that don't support AVIF, hiding hover-only UI, and swapping a heavy chart library for an image on phones. In API Resources, drop fields the mobile client doesn't render — but gate that on an explicit ?client=mobile param the app sends, not on the UA, so caching stays predictable.

Caching strategy. Inside a normal request the built-in cache is free and dies with the request. If you want a shared classification cache, inject a PSR-16 implementation (e.g. symfony/cache's Psr16Cache wrapping Laravel's cache repository) — but remember the key is the UA string, so a Redis-backed cache keyed by UA is effectively a permanent, attacker-growable table. Keep cacheKeyFn at the default sha1 (4.8.09 changed away from base64 precisely because / in keys broke Laravel's file/Redis stores), and add your own size cap.

Testing. Just set the header; no fakes needed:

$this->withHeader('User-Agent', 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) ...')
     ->get('/')
     ->assertSee('Open in the app');

Cover phone, tablet, desktop, and a bot/garbage UA. For unit tests of the detection service itself, call setUserAgent() directly and skip HTTP.

Gotchas and Tips

The "No user-agent has been set" exception (4.8.04-era fix, still a live hazard). In 4.8.01+ the class throws if no UA is available. Under FPM that's fine because autoInitOfHttpHeaders reads $_SERVER, but in a queue worker, an artisan command, an Octane/RoadRunner/FrankenPHP/Swoole worker, or a Http:: call inside a job, $_SERVER['HTTP_USER_AGENT'] is absent or, worse, stale from a previous request on that worker. Always setUserAgent($request->userAgent() ?? '') from the live request. Treat autoInitOfHttpHeaders as a dev convenience, not production behavior.

isMobile() is true for tablets. The single most common classification bug. Phones are isMobile() && ! isTablet(). Write that once in your service and never inline it again.

The internal cache is bounded at 1000 entries (GHSA-mgj4-qjmw-v56v, 4.9.0). Before that fix, reusing one instance across thousands of distinct UAs in a long-running process grew memory without limit. If you're on 4.8.x under Octane, upgrade. On 4.9+ you can also call evictExpired() from a scheduled command if you hold a long-lived instance.

Cache BC break in 4.9.0. Subclasses of Detection\Cache\Cache that narrowed parameter types (function get(string $key, ...)) fatal on class load. If you extended the bundled cache, widen to mixed or drop scalar types. Similarly, Cache::has() now correctly returns false for expired items and lazily deletes them — code that leaned on the buggy true will start missing.

maximumUserAgentLength (default 500) truncates long UAs. Some in-app webviews and bots send enormous UA strings; truncation is a DoS guard but can flip detection on fringe devices. If you see a "misdetected on Android WebView" report, log the raw UA before the truncation point.

version() return type. It defaults to a float (17.0), which loses the minor. Use MobileDetect::VERSION_TYPE_STRING (public since 4.9.0) when you feed the version into a display string or an analytics dimension. Also handle false/empty for engines the regex doesn't know.

Version/branch matrix is a real constraint. 4.10+ → PHP >= 8.2; 4.9.x → PHP 8.2 too (8.0/8.1 dropped from CI); 3.x → PHP 7.4–7.x with the same Detection\MobileDetect namespace. You cannot downgrade 3.x → 2.x without renaming every class reference (\Mobile_Detect), and 2.x/3.x had implicit $_SERVER sniffing with no explicit-UA exception — so upgrade paths quietly fix "it just works in the queue" behavior your code may have depended on.

Detection is advisory and spoofable. A user can send any UA. Don't use it to gate premium content, to hide admin routes, or as a bot filter. For "is this a real browser," use your auth/session layer. For SEO, avoid UA-based redirects that crawlers (Googlebot smartphone UA) will follow blindly — implement rel="alternate" / Vary: User-Agent or, better, keep a single responsive site and use detection only for enhancement.

Extension points worth knowing. Custom cacheKeyFn in config, PSR-16 cache injection, and class-level extensibility (4.9.0 added consistent late static binding) mean you can wrap detection behind a feature-flag interface — useful if you later migrate to a client-hints-first or commercial detector. Note 4.8.07+ already reads Sec-CH-UA-Mobile; if your CDN strips client hints, accuracy silently degrades to UA-only, so don't strip them.

Debugging workflow. When a report says "wrong experience on device X," reproduce by calling setUserAgent() with the exact UA string, then dump isMobile(), isTablet(), is($brand), version('iOS', VERSION_TYPE_STRING). Check the demo at demo.mobiledetect.net to confirm whether the library or your branching logic is at fault — nine times out of ten it's the isMobile()-includes-tablets mistake.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
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
cgv/localized-resource