user-agents: random browser identities weighted by real market share
A JavaScript library for generating random user agents with data that's updated daily.
At a glance
- What is it?
- user-agents is a TypeScript library that generates random user agent strings and browser fingerprints from a dataset updated with new market share data every day, with a filter API to restrict the output. The judgement is that the daily data pipeline is the product, and that the fingerprint fields it generates are the part that will not pass a serious bot detector.
- Who is it for?
- user-agents fits a scraping or load-testing project that needs a large pool of plausible user agent strings drawn from real usage distributions rather than a hardcoded list, and that wants the pool to stay current without anyone maintaining it.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The dataset is the point, and it moves every day
Most user agent generation libraries embed a static list. This one does not, and the difference is the whole point.
The package is a JavaScript module for generating random user agents that is updated daily with new market share data, and a new version of the package is automatically released every day so the data stays current. The README puts the consequence plainly: you can use it without worrying about whether the data will be stale in a matter of months.
The data itself is described as a comprehensive dataset of real-world user agents and other browser properties which are commonly used for browser fingerprinting and blocking automated web browsers. The stated use case is web scraping that needs to create realistic traffic patterns, which generally requires a good source of data.
The version number is the tell. The package manifest declares version `2.1.214`, which is not a semantic version a human chose but a build counter that increments with each daily release. The repository has no GitHub releases, so the npm version is the only version signal there is.
The maintenance picture is healthy in the sense that the code is being worked on: the repository is not archived and the last push was on 2026-10-15. The cadence is the thing to plan around, not the activity level.
new UserAgent() gives you a string and a fingerprint
The basic usage is one constructor call, and the instance is populated automatically.
import UserAgent from 'user-agents';
const userAgent = new UserAgent();
console.log(userAgent.toString());
console.log(JSON.stringify(userAgent.data, null, 2));The two outputs are different things. `toString()` returns the user agent string itself, something like:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36And `data` holds a randomly generated browser fingerprint for more detailed emulation. The example output shows what is in it:
{
"appName": "Netscape",
"connection": {
"downlink": 10,
"effectiveType": "4g",
"rtt": 0
},
"platform": "Win32",
"pluginsLength": 3,
"vendor": "Google Inc.",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
"viewportHeight": 660,
"viewportWidth": 1260,
"deviceCategory": "desktop",
"screenHeight": 800,
"screenWidth": 1280
}The fields fall into three groups. There is the user agent string and its parsed components, `appName`, `platform` and `vendor`. There is the connection information, `downlink`, `effectiveType` and `rtt`, which comes from the Network Information API surface. And there is the display geometry, `viewportHeight`, `viewportWidth`, `screenHeight` and `screenWidth`, plus `pluginsLength` and the `deviceCategory`.
The `data` property is described as a randomly generated browser fingerprint usable for more detailed emulation, and the README notes that the generated data includes hard to find browser-fingerprint properties. That is the claim to hold at arm's length: these are generated values, not values captured from a real browser, so they are plausible and mutually consistent within one record, but they are not a capture of an actual device.
Filters are an object, and reusing one instance is the fast path
The filtering API works by passing an object whose keys correspond to user agent properties.
import UserAgent from 'user-agents';
const userAgent = new UserAgent({ deviceCategory: 'mobile' })Each corresponding property is restricted to the values you give. The example generates a user agent with a `deviceCategory` of `mobile`, and the documentation notes that `desktop` or `tablet` work the same way.
The performance point comes next, and it is a real one for bulk generation. There is some computational overhead in applying a set of filters, so it is far more efficient to reuse the filter initialization when you need many user agents with the same configuration. An initialized `UserAgent` instance can be called like a function, and there is also a `random()` method for people who prefer not to use the shorthand:
import UserAgent from 'user-agents';
const userAgent = new UserAgent({ platform: 'Win32' });
const userAgents = Array(1000).fill().map(() => userAgent());That example initialises a single user agent with a platform filter and then generates 1000 more with the same filter. The alternative, constructing a new `UserAgent` inside the loop, would re-apply the filters every time.
The filters are also not limited to enumerated values. The README documents regular expression matching as a filter, where the generated user agent is guaranteed to match the pattern, which is how you would target a specific browser family or version range that the enumerated properties do not cover.
The documented set of use cases is the same as the property list: a specific platform, a device category, or an operating system version.
A build pipeline that regenerates the data
The repository layout shows how the daily update actually works, and it is more than a static JSON file checked into a package.
The `src/` directory holds the source, and the `package.json` scripts are the whole pipeline. `build` runs `tsup`, and a `postbuild` step runs `yarn gunzip-data` and copies `src/user-agents.json` into `dist/`. The `gunzip-data` script itself is `node --loader ts-node/esm src/gunzip-data.ts src/user-agents.json.gz`, so the data is stored compressed in the repository and expanded at build time.
The `update-data` script is `node --loader ts-node/esm src/update-data.ts src/user-agents.json.gz`, and that is the step that refreshes the dataset.
The `files` array in the manifest is worth reading carefully. It publishes `dist/` and `src/**/*`, but explicitly excludes `src/user-agents.json` and `src/user-agents.json.gz` with negated patterns. So the raw data file is not shipped in the package, only the expanded copy that `postbuild` places into `dist/`.
The `exports` map has two entries, `import` pointing at `./dist/index.js` and `require` pointing at `./dist/index.cjs`, and the manifest declares `"type": "module"`. The `main` field is `./dist/index.cjs` and `module` is `./dist/index.js`, so the package ships both a CommonJS and an ES module build, and the `engines` field requires Node 18 or newer.
For the build tooling, `tsup` is the bundler, with `esbuild` and Babel in the dev dependencies, and the Babel configuration includes the class properties and object rest spread proposals alongside the TypeScript preset, which suggests the source targets environments where those syntax features need transforming.
The licence file and the badge disagree about the licence
There is a discrepancy worth resolving before you adopt this, and it is easy to check.
The badge in the README says BSD 2-Clause, pointing at the `LICENSE` file in the repository root. The `package.json` declares the same thing, with `"license": "BSD-2-Clause"` and `"private": false`. But the repository's licence is reported as a custom one that GitHub cannot classify, which usually means the `LICENSE` file contains wording that the licence detector does not recognise.
There is a plausible explanation: BSD 2-Clause is a two-clause licence, and the `LICENSE` file may carry an additional clause or a preamble that pushes it outside the exact text the detector expects. That would make the detector's warning cosmetic. But it is also possible that the file contains terms a standard BSD 2-Clause does not, and the README badge is out of date. The file at the repository root is what governs, and the README is the thing to check against it.
The author is Intoli, LLC, and the repository links to a blog post on intoli.com explaining how the library works, which is the natural place to look for the data pipeline details that the README does not cover.
Everything else in the repository is conventional for a TypeScript library. There is a `.mocharc.cjs` for the test runner with mocha invoked through `tsx`, an `eslint.config.js` for linting, a `.prettierrc` and `.prettierignore` for formatting, a `tsconfig.json`, a `tsup.config.ts` for the bundler, a `.babelrc` for the Babel transform, a `.tool-versions` file for version management, and a `CLA.md` alongside `CONTRIBUTING.md`.
The topic tags give a sense of the intended use, and they are blunt: `browser-automation`, `user-agent-spoofer` and `web-scraping` are all there, alongside `browsers`, `navigator` and `randomization`.
What this library does not solve
The honest limitation is about detection rather than generation, and the README's own framing helps make the point.
The dataset is weighted by how frequently user agents are used in the wild, so the distribution of what it generates resembles real traffic. That is a meaningful improvement over picking uniformly from a hardcoded list, and the daily refresh keeps the distribution current as browser versions turn over.
What it does not produce is a browser that behaves like the user agent it claims to be. The fingerprint fields in `data` are generated, so a record will have a coherent set of values, but they were not observed on a device. A detection system that correlates the user agent string with the TLS fingerprint, the HTTP header order, the client hints, or the timing behaviour of the requests is looking at signals this library does not touch at all. The header set in the example output even shows a Chrome 68 user agent, which suggests the record set includes older browser versions still present in the usage data.
The practical consequence is that this library makes a request look like it came from a common browser, not like it came from a real one. For a project whose goal is to avoid looking like a scripted client, that is a partial solution with a specific boundary.
The second limitation is operational rather than technical. A package version is published every day, which means a lockfile pinning is either pinned to a stale day or updated daily, and every update is a new artefact to fetch and a new version to review. The `update-data` script exists so that the data can be refreshed without regenerating the whole package, which is the natural way to use this if you want to control the update window yourself.
Editorial conclusion
user-agents fits a scraping or load-testing project that needs a large pool of plausible user agent strings drawn from real usage distributions rather than a hardcoded list, and that wants the pool to stay current without anyone maintaining it. It does not fit a project that needs to defeat active bot detection, since the generated values are random and internally consistent only within one record, and it does not fit a team that needs a stable, auditable dependency, because a new version is published every day. Verify first that the filter API returns what you need for a specific device class, that the `data` fingerprint fields are consistent with the user agent string you get, since they are generated rather than captured from a real browser, and that the daily release cadence is acceptable in your lockfile policy before you pin a version.
Frequently asked questions
How do I install the user-agents package?
Install it from npm with `npm install user-agents`, or with `pnpm install user-agents` or `yarn add user-agents`. The package is published under the name `user-agents` and requires Node 18 or newer.
How do I generate a user agent for a specific device type?
Pass a filter object to the constructor, for example `new UserAgent({ deviceCategory: 'mobile' })`. Each property in the object restricts the corresponding user agent property, and the values can be `mobile`, `desktop` or `tablet` for device category, or a platform such as `Win32`.
How do I generate many user agents with the same filter efficiently?
Initialise one `UserAgent` instance with the filter and then call it as a function, as in `Array(1000).fill().map(() => userAgent())`, or call `userAgent.random()`. Reusing the instance avoids re-applying the filter set on every generation.
How often is the user-agents package updated?
A new version of the package is automatically released every day with new market share data, and the version in the package manifest is a build counter at `2.1.214`. The repository has no GitHub releases, so the npm version is the only version signal.
What is the licence of the user-agents package?
The README badge and the package manifest both say BSD-2-Clause, and there is a `LICENSE` file at the repository root. The repository's licence is reported as a custom one that GitHub cannot classify, so the `LICENSE` file is the text to read before relying on the badge.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/intoli-user-agents)Was this useful?