How to Detect Mobile vs Desktop Visitors (And When the IP Helps)

Reliable methods to detect mobile vs desktop visitors — User-Agent parsing, client hints, IP/ASN signals — with the gotchas and the limits of each.

How to Detect Mobile vs Desktop Visitors (And When the IP Helps)

If you want to know whether your visitor is on mobile or desktop, the obvious answer is “parse the User-Agent string.” That works most of the time, but it’s surprisingly fragile, increasingly opt-in, and gives you less than you might expect. The IP layer — specifically the ASN the user is connecting from — adds an important secondary signal when User-Agent isn’t enough.

This post walks through the practical approaches: User-Agent parsing, modern client hints, IP/ASN-based heuristics, and the combined patterns that work in production.

Why You Want to Know

A few use cases for “is this mobile?”:

  • Responsive design fallbacks. CSS handles most of this, but server-side rendering may need to choose templates per device.
  • Analytics segmentation. Distinguishing mobile and desktop traffic is fundamental for product analytics.
  • Mobile-specific features. Push notifications, app-store deeplinks, mobile-only download CTAs.
  • Conversion-rate optimization. Mobile and desktop convert differently — measuring each separately matters.
  • Performance budgets. Different image sizes, different feature levels for mobile clients.

The accuracy bar isn’t “100%.” It’s “right often enough to be useful for the specific decision you’re making.”

Method 1: User-Agent String Parsing

The traditional approach. Every browser sends a User-Agent header that includes device hints:

Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

The patterns:

  • Mobile: Usually includes Mobile, Android, iPhone, iPad, iPod.
  • Desktop: Includes Windows, Macintosh, Linux x86_64.
  • Tablet: iPad or Android without Mobile (yes, Android tablets specifically omit Mobile).

A naive parse:

function isMobile(ua) {
    return /Mobile|Android|iPhone|iPod/i.test(ua)
}

function isTablet(ua) {
    return /iPad|Tablet|Android(?!.*Mobile)/i.test(ua)
}

Works for ~95% of traffic. For production, use a library like ua-parser-js (Node), user-agents (Python), or your framework’s built-in (Laravel has Agent).

Pros:

  • Available on every request (well, every request that sends a UA).
  • Server-side, no client cooperation needed.
  • Mature libraries that handle the edge cases.

Cons:

  • Slow erosion. Browsers (Chrome especially) are reducing UA detail over time. Future UAs will be less specific.
  • Easy to spoof. Headless browsers, scrapers, and corporate-managed browsers can send anything.
  • Doesn’t distinguish “phone-sized tablet” from “phone” or “small laptop” from “tablet.”
  • Inconsistent with new device types. Foldables, gaming handhelds, anything new — coverage in libraries lags.

Method 2: Client Hints

The modern replacement for User-Agent. Servers request specific hints via Accept-CH; browsers respond with Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA, etc.

Response: Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform

Subsequent request:
Sec-CH-UA-Mobile: ?1                    (1 = mobile, 0 = not)
Sec-CH-UA-Platform: "Android"
Sec-CH-UA: "Google Chrome";v="120"

In your backend:

const isMobile = req.headers['sec-ch-ua-mobile'] === '?1'
const platform = req.headers['sec-ch-ua-platform']?.replace(/"/g, '')

Pros:

  • More accurate than UA parsing for the specific properties exposed.
  • Future-proof — UA is being deprecated; client hints are the replacement.
  • Easier to parse (structured headers, not regex).

Cons:

  • Requires a round trip to request the hints first (most browsers don’t send them by default).
  • Not all browsers support them yet. Safari support is partial.
  • Easier for privacy tools to strip than UA.

For production in 2026, use client hints when they’re sent, fall back to UA parsing otherwise.

Method 3: IP / ASN-Based Heuristics

Where the IP layer adds value. The ASN — and the type of ASN — gives signals about the user’s network:

  • Mobile carrier ASN (T-Mobile, Verizon Wireless, Vodafone, Reliance Jio, etc.) → almost certainly mobile data, almost certainly a mobile device.
  • Residential ISP ASN (Comcast, BT, Deutsche Telekom) → could be either; favor UA signal.
  • Business / fiber ASN → desktop heavily favored.
  • Cloud / hosting ASN (AWS, GCP, Azure, Hetzner) → bot, automation, or developer.
  • VPN ASN → user could be anything; signal is unreliable.

You can look up an IP’s ASN cheaply and classify it:

const result = await convertIP(ip)
const asnType = classifyAsn(result.data.asn.number, result.data.asn.name)

if (asnType === 'mobile') {
    // Very likely mobile device on cellular data
}
if (asnType === 'hosting') {
    // Almost certainly not a real user
}

The classification logic itself is a curated list — major mobile carriers, major hosting providers, major VPN services. Services like Ip2Geo’s API include this classification inline; you don’t have to build it.

Pros:

  • Cannot be spoofed by the user (you read the IP from your network layer, not from a header).
  • Useful when User-Agent is missing or stripped.
  • Catches obvious automation (cloud IPs) that look like normal browsers in UA.

Cons:

  • Coarse-grained. Tells you “user is on a phone network” but not “phone vs tablet on that network.”
  • Mobile vs non-mobile is fuzzy on shared infrastructure like enterprise Wi-Fi which routes via the corporate network’s ISP.
  • CGNAT obscures details (see the NAT post).

The Combined Strategy

A robust detection looks at all three signals:

async function detectDevice(req: Request, ip: string) {
    // 1. Modern client hints (best signal when present)
    const mobileHint = req.headers['sec-ch-ua-mobile']
    if (mobileHint === '?1') return 'mobile'
    if (mobileHint === '?0') return 'desktop'
    
    // 2. User-Agent parsing (default for most current traffic)
    const ua = req.headers['user-agent'] ?? ''
    if (/iPad|Android(?!.*Mobile)|Tablet/i.test(ua)) return 'tablet'
    if (/Mobile|Android|iPhone|iPod/i.test(ua)) return 'mobile'
    
    // 3. IP/ASN fallback (when UA is missing or suspicious)
    const result = await convertIP(ip)
    if (result.success) {
        const asnType = classifyAsn(result.data.asn.number)
        if (asnType === 'mobile') return 'mobile'
        if (asnType === 'hosting') return 'bot'
    }
    
    // 4. Default
    return 'desktop'
}

For most apps, the order matters more than the details. Use the strongest available signal first.

Common Mistakes

Trusting User-Agent blindly

A scraper sending Mozilla/5.0 (iPhone...) will be classified as mobile. If your decision changes business outcomes (showing different pricing, etc.), this is exploitable.

Treating tablets as desktop

Default iPad and Android tablets often have a desktop-class UA but a touch interface. Categorize them explicitly.

Server-side decisions that should be client-side

Responsive design happens in CSS. Don’t render a completely different HTML page for mobile when CSS media queries can handle it. Server-side mobile/desktop detection should be reserved for things CSS can’t do (template choice, feature flags).

Caching the wrong way

If you cache a page server-side rendered for mobile, don’t serve it to desktop visitors. Vary the cache by User-Agent device class or skip caching for variable content.

Forgetting bot traffic

Scrapers, search engines, monitoring tools — none of these are “mobile” or “desktop” in a meaningful sense. Detect bots first via UA pattern matching (bot, crawler, spider) and ASN (hosting providers are usually bots) before classifying the rest.

What Each Signal Tells You

A quick reference:

SignalTells you…Reliable when…Spoofable?
UA stringBrowser, OS, device hintsUA is detailed and presentYes
Client hintsStructured device infoBrowser supports themLess easily
ASN classificationNetwork typeYou have a curated listNo
IP geolocationCountry, regionAlwaysNo (without VPN)
Screen size (JS)Actual viewportClient cooperatesYes
Touch events (JS)Touch capabilityClient cooperatesYes

The strongest single signal is server-side ASN classification — you can’t spoof your network of origin. The most precise signal is client-side fingerprinting (touch events, screen size, hover capability) — which requires JavaScript to run.

For SEO and bot detection, combine all of the above.

A Concrete Implementation

Putting it together for a real app:

import { Request } from 'express'
import { convertIP, init } from '@ip2geo/sdk'

init({ authKey: process.env.IP2GEO_API_KEY! })

const HOSTING_ASNS = new Set([16509, 14061, 15169, 8075, ...])  // AWS, DO, Google, Microsoft, etc.
const MOBILE_ASNS = new Set([20057, 22394, 21928, ...])  // Major mobile carriers

export type DeviceClass = 'mobile' | 'tablet' | 'desktop' | 'bot' | 'unknown'

export async function classifyDevice(req: Request): Promise<DeviceClass> {
    const ua = (req.headers['user-agent'] ?? '').toLowerCase()
    
    // 1. Quick bot detection
    if (/bot|crawler|spider|httpclient|wget|curl/i.test(ua)) return 'bot'
    
    // 2. ASN check for non-residential IPs
    try {
        const result = await convertIP(req.ip)
        if (result.success && result.data?.asn?.number) {
            const asn = result.data.asn.number
            if (HOSTING_ASNS.has(asn)) return 'bot'
        }
    } catch {} // continue if API fails
    
    // 3. Tablet detection (specific Android tablets and iPad)
    if (/ipad/.test(ua) || /android(?!.*mobile)/.test(ua)) return 'tablet'
    
    // 4. Mobile via UA
    if (/mobile|android|iphone|ipod/.test(ua)) return 'mobile'
    
    // 5. Modern client hints
    const mobileHint = req.headers['sec-ch-ua-mobile']
    if (mobileHint === '?1') return 'mobile'
    
    // 6. Default to desktop
    return 'desktop'
}

This catches the major cases, fails open (returns ‘desktop’ rather than crashing), and uses ASN data as backup when UA is missing or stripped.

TL;DR

  • User-Agent parsing is the default but is being deprecated by browsers.
  • Client hints (Sec-CH-UA-Mobile) are the replacement — use them when available.
  • IP/ASN classification adds a fraud-resistant signal when UA can’t be trusted.
  • Combine all three for the most robust detection.
  • Don’t make destructive decisions on this signal — it’s a heuristic, not a verdict.
  • Cache lookups if you’re checking ASN per-request.
  • Detect bots first, then classify the rest.

For more on what the IP layer can tell you, see How to Geolocate an IP and What is an ASN. To try ASN-based classification on real traffic, the Ip2Geo API returns the classified ASN type inline with every geo lookup.

Get Started

Convert IPs into accurate location data in milliseconds.

Sign up today and get 1,000 free monthly stored conversions, and discover why developers trust us for fast, reliable, and affordable IP conversions.