--- url: https://undevice.hacks.run/guide/agent-skill.md --- # Agent skill The skill helps a coding agent use undevice, configure extensions, and connect detection to request handlers. ```sh npx skills add teplostanski/undevice --skill undevice ``` [Skill source](https://github.com/teplostanski/undevice/tree/main/skills/undevice). Machine-readable documentation is available as [llms.txt](/llms.txt) and [llms-full.txt](/llms-full.txt). --- --- url: https://undevice.hacks.run/guide/cdn.md --- # CDN ## CloudFront When the User-Agent matches `Amazon CloudFront` case-insensitively, the following headers are checked: * `CloudFront-Is-Android-Viewer` * `CloudFront-Is-Desktop-Viewer` * `CloudFront-Is-IOS-Viewer` * `CloudFront-Is-Mobile-Viewer` * `CloudFront-Is-Tablet-Viewer` Pass request headers via `headers`. If no device type header is `true`, the result includes `isUnknown: true`. > \[!CAUTION] > CloudFront does not provide headers for detecting Windows or macOS. [Configure device type headers in CloudFront](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/adding-cloudfront-headers.html#cloudfront-headers-device-type). ## Cloudflare If the CloudFront rule does not apply and `CF-Device-Type` is `mobile`, `tablet`, or `desktop`, that value takes precedence over the User-Agent for device type detection. If the header is missing or its value is unrecognized, detection falls back to the User-Agent. [Configure Cache by device type in Cloudflare APO](https://developers.cloudflare.com/automatic-platform-optimization/reference/cache-device-type/). --- --- url: https://undevice.hacks.run/guide/custom-flags.md --- # Custom flags Pass an object to `undevice()` to add your own flags. Each key becomes a flag name in the result. Each value can be: * A function `(ua: string) => boolean`. It receives the User-Agent trimmed and converted to lowercase. * An array of strings for case-insensitive substring matching against the User-Agent. The check returns `true` if at least one substring matches. Special characters such as `.` and `*` are treated literally. * An object with a required `detect` and an optional `exclude`. Both fields accept a function or an array of strings. If `exclude` returns `true`, the flag is set to `false` and `detect` is skipped. Otherwise, the flag takes the result of `detect`. A check function: ```ts const { detect } = undevice({ isCrawler: (ua) => /googlebot/i.test(ua), }); ``` An array of substrings: ```ts const { detect } = undevice({ isRawClient: ["curl", "wget"], }); ``` A check with an array of exclusions: ```ts import { isbot } from "isbot"; const { detect } = undevice({ isCrawler: { detect: isbot, exclude: ["curl", "wget"], }, }); ``` An array of substrings in `detect` and a function in `exclude`: ```ts const { detect } = undevice({ isCrawler: { detect: ["bot", "crawler", "spider"], exclude: (ua) => ua.includes("my-health-checker"), }, }); ``` #### Examples with third-party libraries You can use a function from a third-party library as a check or use its data in your own callback. The examples below use `isbot` and `crawler-user-agents`. **isCrawler with isbot** Use [isbot](https://github.com/omrilotan/isbot) to identify bot User-Agents: ```bash npm install isbot ``` ```ts import { isbot } from "isbot"; import { undevice } from "undevice"; const { detect } = undevice({ isCrawler: isbot }); const device = detect({ headers: request.headers }); device.isCrawler; // boolean ``` **isCrawler with crawler-user-agents** You can also build a check from the patterns in [crawler-user-agents](https://github.com/monperrus/crawler-user-agents). Compile the regular expressions once when setting up the detector: ```bash npm install crawler-user-agents ``` ```ts import crawlers from "crawler-user-agents" with { type: "json" }; import { undevice } from "undevice"; const patterns = crawlers.map(({ pattern }) => new RegExp(pattern, "i")); const { detect } = undevice({ isCrawler: (ua) => patterns.some((pattern) => pattern.test(ua)), }); const device = detect({ headers: request.headers }); device.isCrawler; // boolean ``` ## Type inference ```ts twoslash import { undevice } from "undevice"; const { detect } = undevice({ isRawClient: ["curl", "wget"] }); const device = detect({ userAgent: "curl/8.0" }); device.isRawClient; // ^? ``` --- --- url: https://undevice.hacks.run/api/functions/undevice.md --- [undevice](../index.md) / undevice # Function: undevice() > **undevice**<`T`>(`extensions?`): `Readonly`<{ `detect`: (`options`) => [`UndeviceResult`](../type-aliases/UndeviceResult.md)<`T`>; }> Defined in: [undevice.ts:19](https://github.com/teplostanski/undevice/blob/e23a78421710958df6c38902428aa8e6d1753c05/src/core/undevice.ts#L19) Creates a reusable detector with optional custom boolean flags. Each call to `detect` returns a new frozen result for the supplied request. ## Type Parameters ### T `T` *extends* `Extensions` = `Record`<`never`, `never`> ## Parameters ### extensions? `T` Named callbacks, substring arrays, or checks with exclusions. ## Returns `Readonly`<{ `detect`: (`options`) => [`UndeviceResult`](../type-aliases/UndeviceResult.md)<`T`>; }> ## Example ```ts const { detect } = undevice({ isRawClient: ['curl', 'wget'] }) const device = detect({ userAgent: 'curl/8.0' }) ``` --- --- url: https://undevice.hacks.run/guide/getting-started.md --- # Getting started These docs describe the 1.0.0-beta API. Install the beta release to use the factory and extensions. ::: code-group ```sh [npm] npm install undevice@beta ``` ```sh [pnpm] pnpm add undevice@beta ``` ```sh [yarn] yarn add undevice@beta ``` ```sh [bun] bun add undevice@beta ``` ::: ## First detection Create a detector once, then pass each incoming request's headers to `detect()`: ```ts import { undevice } from "undevice"; const { detect } = undevice(); const device = detect({ headers: request.headers }); device.isMobile; device.isTablet; device.isDesktop; ``` `undevice()` returns a frozen object with a `detect` method. Each call to `detect()` returns a new frozen [UndeviceResult](/api/type-aliases/UndeviceResult) with detection [flags](/guide/detection#result). The library does not mutate input data or implicitly access request context or browser globals. ### User-Agent In the browser, pass `navigator.userAgent`: ```ts import { undevice } from "undevice"; const { detect } = undevice(); const device = detect({ userAgent: navigator.userAgent }); ``` To override the User-Agent: ```ts const device = detect({ headers: request.headers, userAgent: customUserAgent, }); ``` For SSR, reuse the server's result on the client to avoid hydration mismatches. --- --- url: https://undevice.hacks.run/guide/detection.md --- # Input and result `detect(options)` accepts the options below. Pass `{}` without headers or a User-Agent to get an unknown device type. ```ts type UndeviceOptions = Readonly<{ headers?: RequestHeaders; userAgent?: string; }>; ``` | Option | Type | Default | Description | | --- | --- | --- | --- | | `headers` | [`RequestHeaders`](/api/type-aliases/RequestHeaders) | `undefined` | Request headers. Accepts a standard [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) instance or a plain record, including Node.js request headers. | | `userAgent` | `string` | From `headers` | Used instead of the `user-agent` header value. Also supports standalone UA detection. | ### Result [`UndeviceResult`](/api/type-aliases/UndeviceResult) contains the following built-in boolean flags: | Group | Flags | | --- | --- | | Device | `isMobile`, `isTablet`, `isDesktop`, `isUnknown` | | Combined | `isMobileOrTablet`, `isDesktopOrTablet` | | OS | `isIos`, `isAndroid`, `isWindows`, `isMacOS`, `isLinux`, `isApple` | | Browser | `isChrome`, `isFirefox`, `isSafari`, `isEdge`, `isSamsung` | `isUnknown` is `true` when the input does not resolve to a device type. Custom flags are added to the same result object. --- --- url: https://undevice.hacks.run/guide/integrations.md --- # Integrations and SSR Create the detector outside the request handler. Keep each result local to its request. The examples below use the core API directly; no framework adapter package is required. ## Express and Fastify Both expose a Node.js header record as `request.headers`. Use it in your existing route: ```ts import { undevice } from 'undevice'; const { detect } = undevice(); // Inside an Express or Fastify request handler: const device = detect({ headers: request.headers }); ``` Return the flags, pass them to your template, or store them in the framework's request-local context. Do not put the latest request's result in a shared module variable. ## Astro Use a server-rendered route to detect the current visitor: ```astro --- import { undevice } from 'undevice'; export const prerender = false; const { detect } = undevice(); const device = detect({ headers: Astro.request.headers }); ---
Mobile: {String(device.isMobile)}
``` For reuse across routes, export a configured detector from an application module. A prerendered page has no per-visitor headers at build time. ## Fetch handlers ```ts twoslash import { undevice } from 'undevice'; const { detect } = undevice(); export function handleRequest(request: Request): Response { return Response.json(detect({ headers: request.headers })); } ``` ## Hydration Pass the server result to the client for its first render. Independently detecting on both sides can produce different flags, especially when the server has CDN headers that the browser does not have. ::: tip Device detection is a hint Use CSS media queries for responsive layout. Use device flags when application behavior specifically needs information about the client. User-Agent strings are supplied by the client and are not authentication signals. ::: --- --- url: https://undevice.hacks.run/playground.md --- # Playground Your browser’s User-Agent is loaded automatically. Choose a sample or enter another User-Agent. Detection runs locally in your browser; the string is not sent anywhere. This demo adds `isRawClient` (curl and wget) and `isCrawler` (Googlebot only). These are example checks, not a full bot catalog. The playground does not supply CDN headers. --- --- url: https://undevice.hacks.run/api/type-aliases/RequestHeaders.md --- [undevice](../index.md) / RequestHeaders # Type Alias: RequestHeaders > **RequestHeaders** = `Readonly`<`Record`<`string`, `string` | readonly `string`\[] | `undefined`>> | `Readonly`<{ `get`(`name`): `string` | `null`; }> Defined in: [types.ts:6](https://github.com/teplostanski/undevice/blob/e23a78421710958df6c38902428aa8e6d1753c05/src/core/types.ts#L6) Standard Headers, a compatible reader, or a Node.js-style header record. --- --- url: https://undevice.hacks.run/api/type-aliases/UndeviceOptions.md --- [undevice](../index.md) / UndeviceOptions # Type Alias: UndeviceOptions > **UndeviceOptions** = `Readonly`<{ `headers?`: [`RequestHeaders`](RequestHeaders.md); `userAgent?`: `string`; }> Defined in: [types.ts:11](https://github.com/teplostanski/undevice/blob/e23a78421710958df6c38902428aa8e6d1753c05/src/core/types.ts#L11) Input for one detection call. An explicit UA overrides the UA header. --- --- url: https://undevice.hacks.run/api/type-aliases/UndeviceResult.md --- [undevice](../index.md) / UndeviceResult # Type Alias: UndeviceResult\