Device Reference

January 20, 2026 ยท View on GitHub

The device module provides User-Agent-based device detection.

Quick Checks

isMobile

pub fn isMobile(allocator: Allocator, user_agent: []const u8) !bool

Check if the User-Agent represents a mobile device.

const device = zigly.device;

const ua = try request.headers.get(allocator, "User-Agent");
if (try device.isMobile(allocator, ua)) {
    // Mobile device
}

isTablet

pub fn isTablet(allocator: Allocator, user_agent: []const u8) !bool

Check if the User-Agent represents a tablet.

isDesktop

pub fn isDesktop(allocator: Allocator, user_agent: []const u8) !bool

Check if the User-Agent represents a desktop device.


Full Detection

lookup

pub fn lookup(
    allocator: Allocator,
    user_agent: []const u8,
    buf: []u8
) !std.json.Parsed(DetectionResult)

Get complete device information.

var buf: [4096]u8 = undefined;
const result = try device.lookup(allocator, user_agent, &buf);
defer result.deinit();

const dev = result.value.device;
if (dev.brand) |brand| {
    std.debug.print("Brand: {s}\n", .{brand});
}

lookupRaw

pub fn lookupRaw(user_agent: []const u8, buf: []u8) ![]const u8

Get the raw JSON response from device detection.

var buf: [4096]u8 = undefined;
const json = try device.lookupRaw(user_agent, &buf);
// Parse or inspect json directly

DetectionResult

The parsed detection result.

pub const DetectionResult = struct {
    device: Device = .{},
};

Device

Device properties from detection.

pub const Device = struct {
    name: ?[]const u8 = null,         // "iPhone", "Galaxy S21"
    brand: ?[]const u8 = null,        // "Apple", "Samsung"
    model: ?[]const u8 = null,        // Device model
    hwtype: ?[]const u8 = null,       // "Mobile Phone", "Tablet", "Desktop"
    is_mobile: ?bool = null,          // Mobile phone
    is_tablet: ?bool = null,          // Tablet
    is_desktop: ?bool = null,         // Desktop/laptop
    is_smarttv: ?bool = null,         // Smart TV
    is_gameconsole: ?bool = null,     // Game console
    is_ereader: ?bool = null,         // E-reader
    is_mediaplayer: ?bool = null,     // Media player
    is_tvplayer: ?bool = null,        // TV player/set-top box
    is_touchscreen: ?bool = null,     // Has touchscreen
};

All fields are optional because detection may not identify all properties.


Example Usage

Device-Based Routing

const std = @import("std");
const zigly = @import("zigly");
const device = zigly.device;

fn start() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    var downstream = try zigly.downstream();

    const ua = downstream.request.headers.get(allocator, "User-Agent") catch "";

    if (try device.isMobile(allocator, ua)) {
        try downstream.proxy("mobile_backend", "m.example.com");
    } else if (try device.isTablet(allocator, ua)) {
        try downstream.proxy("tablet_backend", "tablet.example.com");
    } else {
        try downstream.proxy("desktop_backend", "www.example.com");
    }
}

Device Information Headers

fn start() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    var downstream = try zigly.downstream();

    const ua = downstream.request.headers.get(allocator, "User-Agent") catch "";

    var buf: [4096]u8 = undefined;
    if (device.lookup(allocator, ua, &buf)) |result| {
        defer result.deinit();
        const dev = result.value.device;

        if (dev.brand) |brand| {
            try downstream.request.headers.set("X-Device-Brand", brand);
        }
        if (dev.name) |name| {
            try downstream.request.headers.set("X-Device-Name", name);
        }
        if (dev.is_touchscreen) |ts| {
            try downstream.request.headers.set("X-Has-Touch", if (ts) "true" else "false");
        }
    } else |_| {}

    try downstream.proxy("origin", null);
}

Cache Key Based on Device

fn start() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    var downstream = try zigly.downstream();

    const ua = downstream.request.headers.get(allocator, "User-Agent") catch "";

    const device_class = if (try device.isMobile(allocator, ua))
        "mobile"
    else if (try device.isTablet(allocator, ua))
        "tablet"
    else
        "desktop";

    // Add to Vary for proper caching
    try downstream.request.headers.set("X-Device-Class", device_class);

    try downstream.proxy("origin", null);
}

Testing

Device detection works locally. Test with different User-Agent strings:

# iPhone
curl -H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15" \
     http://localhost:7878/

# Android
curl -H "User-Agent: Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 Chrome/100.0" \
     http://localhost:7878/

# iPad
curl -H "User-Agent: Mozilla/5.0 (iPad; CPU OS 15_0 like Mac OS X) AppleWebKit/605.1.15" \
     http://localhost:7878/

# Desktop Chrome
curl -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/100.0" \
     http://localhost:7878/