express-useragent moves User-Agent parsing into the Express middleware chain
NodeJS user-agent middleware
At a glance
- What is it?
- express-useragent is a small MIT licensed TypeScript library that parses User-Agent headers into a normalised snapshot and, as Express middleware, hydrates req.useragent and res.locals.useragent on every request. Version 2 changed the default export, so the upgrade path matters, and the bot detection list is maintained by hand through pull requests.
- Who is it for?
- express-useragent suits an Express codebase that wants a typed user-agent snapshot on every request without writing a parser, and it is the wrong choice for a service that only needs the raw header, since the middleware mutates the request for everyone on the chain.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A parser that can also be middleware
express-useragent is a fast user-agent parser with first-class Express middleware and TypeScript typings. It runs server-side in Node.js and in the browser through a lightweight IIFE bundle, and it requires Node.js 18 or newer.
The audience is an Express developer who needs to know whether a request came from a phone, a desktop or a crawler, without pulling in a heavier dependency or writing regexes. The library is MIT licensed, authored by Aleksejs Gordejevs, and published to npm as express-useragent. The documentation site hosts a live demo, and the repository keeps a changelog and a contributing guide.
The design is worth understanding before the code, because it explains the API shape. There is a parser object, and there is a piece of Express glue. The parser is what does the work, and the middleware is a thin wrapper that runs the parser for you on each request and hangs the result on the request object. That is why the same package can be used in a browser where there is no Express at all.
Maintenance is steady rather than dramatic. The last push was on 2026-10-21, and the recent tags are v2.2.3 on 2026-08-22, v2.2.2 on 2026-08-13 and v2.2.1 on 2026-06-30.
Four API entry points and one normalised snapshot
The API highlights section of the README lists four things, and they cover every way you would use the library.
The first is new UserAgent(), which builds a fresh parser instance. The second is useragent.parse(source), a quick parse that returns the agent snapshot. The third is useragent.express(), the Express-compatible middleware, and the documentation is precise about what it does: it hydrates req.useragent and res.locals.useragent. The fourth is parser.Agent, the normalised fingerprint, which carries convenience booleans such as isMobile and isBot.
The snapshot shape is the useful part, and the README shows a sample payload:
{
"isMobile": false,
"isDesktop": true,
"isBot": false,
"browser": "Chrome",
"version": "118.0.0",
"os": "macOS Sonoma",
"platform": "Apple Mac",
"source": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0)..."
}The original header is kept in the source field, which matters when you need to log exactly what arrived. The rest is the interpretation: a browser name, a version, an operating system, a platform, and the device-class booleans that most applications actually branch on.
Using the middleware in an ESM project takes one import and one line:
import express from 'express';
import { express as useragent } from 'express-useragent';
const app = express();
app.use(useragent());and the handler then reads req.useragent, with the browser and operating system fields as the typical request.
Version 2 broke the default import on purpose
The migration notes are the part of the README with the most practical value, because a v1 to v2 upgrade changes how the package is imported.
In v1.x, a default import returned an object with an .express() method that was used as middleware. In v2.x, the default export is a parser instance for direct parsing, and the Express middleware moved to a named export called express, with an alias useragentMiddleware. The README gives the three supported shapes: a named import of express used directly as app.use(useragent()), a namespace import used as app.use(useragent.express()), and the CommonJS form.
CommonJS is the smoothest path, since require('express-useragent').express() continues to work unchanged. The namespace import is the migration for code that wants the middleware under its old name, and the named import is the one the documentation leads with.
The namespace form is worth looking at, because it is the one that keeps the old shape:
import express from 'express';
import * as useragent from 'express-useragent';
const app = express();
app.use(useragent.express());A code base that upgrades without touching its imports will find that the middleware is no longer where it used to be, and the failure will show up at startup rather than as a subtle behaviour change. The repository also ships end-to-end demos under examples/, a server.ts file for the Express middleware and an http.ts file for a raw Node HTTP sample, which is the fastest way to confirm the import shape in your own project.
Drop-in browser bundles for the same parser
The browser story is unusually complete for a server-side library, and it works because the parser is separate from the middleware.
The build exports two drop-in bundles under dist/browser/. The first, express-useragent.global.js, is a readable IIFE that exposes window.UserAgent and window.useragent. The second, express-useragent.global.min.js, is the minified version of the same API. A page can therefore include one script tag and parse a browser's own user agent:
<script src="/vendor/express-useragent.global.min.js"></script>
<script>
const agent = new UserAgent().parse(navigator.userAgent);
console.log(agent.browser, agent.version);
</script>The documentation still recommends consuming the ESM or CommonJS entry point from your bundler when possible, since the global bundles exist for pages that have no build step:
import { UserAgent } from 'express-useragent';
const agent = new UserAgent().parse(navigator.userAgent);That is the same call as on the server, with the source string coming from navigator.userAgent instead of a request header. The practical use is a client-side hint, for example choosing a layout, and the fact that the parsing rules match the server side means the two cannot disagree about what a device is.
The build, the test suite and the repository layout
The scripts section documents the whole developer workflow, and it is short:
npm install # install dependencies
npm run lint # lint the TypeScript sources, tests, and examples
npm run typecheck # run the TypeScript compiler in noEmit mode
npm test # execute Vitest (includes adapted legacy suites)There are also three runnable examples for manual testing, a raw Node HTTP sample, an Express middleware demo and a CLI parsing helper, invoked with npm run http, npm run express and npm run simple respectively.
The package metadata shows how the artefact is produced. The CommonJS entry point is dist/index.cjs, the ES module entry is dist/index.mjs and the type declarations are dist/index.d.ts, and the build script emits the dist directory with the CommonJS output, the ES module output, the declarations and the browser bundles. The build is driven by tsup, the tests by Vitest, and the test script builds first and then runs Vitest with a dedicated config, which is why the test command touches dist before it starts.
The rest of the tree is conventional for a modern TypeScript package: an eslint flat config, a Prettier configuration, a tsconfig, a vitest config, examples/, tests/, scripts/, docs/ and a CHANGELOG.md. The repository also keeps a .travis.yml from the project's earlier years alongside the GitHub Actions workflow that now runs the CI badge in the README.
Bot lists are maintained by hand, and v1 habits break in v2
Two practical limitations deserve to be stated plainly.
The first is the bot list. The contributing guide has a section on how to update it, which means the set of recognised crawlers is maintained by hand through pull requests rather than pulled from a maintained feed. The snapshot exposes isBot as a convenience boolean, and the value of that boolean depends entirely on the accuracy and freshness of that list, which is a property of the repository rather than of the algorithm.
The second is the import change described earlier. The v2 default export is a parser instance, so code written against v1 that did import useragent from 'express-useragent' will call .express() on something that no longer has it, and the fix is the named import or the namespace import. The README documents this, and the changelog notes for breaking updates are requested in the contribution guidelines, so the information exists, but it is something a reader has to look for rather than something the package can enforce.
A third point is about scope rather than defect. The documentation does not describe the parsing rules, the device classification table or how the operating system and platform strings are derived. If your application needs to branch on something specific, such as a particular browser version or an unusual platform string, the safe approach is to read the source field, which the snapshot keeps, and apply your own rule on top rather than assuming a particular derived value.
Middleware, a direct parse, or your own regex
There are three reasonable ways to get user-agent information into an Express application, and they differ in what they cost you.
The first is the middleware, which is what this package is for. Every request in the chain pays for the parse, and every handler downstream can read req.useragent without a parameter. That convenience is the trade, because a request that arrives at a route which does not care about the user agent is still parsed, and the middleware sits in the chain whether or not you need it.
The second is to use the direct parse API in the one or two handlers that care. Creating a parser instance and calling parse on the header gives the same snapshot without touching every request, at the cost of wiring the header through yourself.
The third is to write the parsing yourself. A user agent is a string, and the cases you care about, a specific bot, a specific platform, a mobile device, are often a substring test. That approach has no dependency and no chain cost, and it fails in a specific way: it only knows the patterns you wrote, so every new device class is a change in your code, and the classification is not shared with the client side the way it is here.
The argument for this library is therefore about coverage and consistency rather than raw speed. The argument against it is that a middleware that runs for every request in an application that only checks the user agent in one route is doing work nobody asked for.
Editorial conclusion
express-useragent suits an Express codebase that wants a typed user-agent snapshot on every request without writing a parser, and it is the wrong choice for a service that only needs the raw header, since the middleware mutates the request for everyone on the chain. Before adopting it, read the version 2 migration notes in the README, because the default export is now a parser instance rather than the middleware, and check the bot list maintenance guidance in CONTRIBUTING.md if bot filtering matters to you.
Frequently asked questions
What does express-useragent do?
It parses a User-Agent string into a normalised snapshot with booleans such as isMobile, isDesktop and isBot, plus browser, version, os, platform and the original source string. As Express middleware it hydrates req.useragent and res.locals.useragent on each request.
How do I install express-useragent and add it to an Express app?
Install it with npm install express-useragent, which requires Node.js 18 or newer. In an ESM project import { express as useragent } from 'express-useragent' and call app.use(useragent()), while CommonJS code keeps using require('express-useragent').express().
What changed between version 1 and version 2 of express-useragent?
In v1.x the default export was an object with an .express() method used as middleware. In v2.x the default export is a parser instance and the middleware is a named export called express, with the alias useragentMiddleware, so a named import or a namespace import is needed. The CommonJS require pattern is unchanged.
Can express-useragent be used in a browser?
Yes. The build ships drop-in IIFE bundles under dist/browser/, one readable and one minified, that expose window.UserAgent and window.useragent. The documentation still recommends importing the ESM or CommonJS entry from your bundler when you have one.
How are bots detected by express-useragent?
The parsed snapshot exposes an isBot boolean, and the contributing guide explains how to update the bot list, which means the list of recognised crawlers is maintained by hand through pull requests rather than fetched from a feed.
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/biggora-express-useragent)Was this useful?