device_detector parses user agents in Ruby using Matomo's regex database
DeviceDetector is a precise and fast user agent parser and device detector written in Ruby
At a glance
- What is it?
- DeviceDetector is a Ruby user agent parser and device detector maintained by the team behind Podigee, adapted from Matomo's Universal Device Detection library and shipped with the same unchanged regex YAML files so both sides benefit from upstream updates. It detects client, operating system, device type, brand and model, and can use client hint headers to improve accuracy.
- Who is it for?
- Ruby applications that need to know what kind of client is connecting should adopt device_detector, because it ships the Matomo regex database unchanged, which means coverage of obscure browsers and devices is far wider than a hand written parser, and it accepts client hint headers to resolve the cases where the user agent string lies.
- 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 last received commits 47 days ago.
- What is it written in?
- Mainly Ruby, 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 port that deliberately keeps the upstream data
DeviceDetector is a precise and fast user agent parser and device detector written in Ruby, backed by what the README calls the largest and most up-to-date user agent database.
The data is the point. The project originated as a Ruby port of the Universal Device Detection library written and maintained by Matomo Analytics, and the README's disclaimer makes the relationship explicit: this port does not aspire to be a one-to-one copy from the original code, but rather an adaptation for the Ruby language.
And then it states the goal that makes the arrangement work: the goal is to use the original, unchanged regex YAML files for user agent detection provided by the upstream version, in order to mutually benefit from updates and pull requests to both the original and the ported versions.
That is an unusual and sensible division of labour. The hard part of user agent detection is not the Ruby code, it is the thousands of regular expressions describing how every browser, bot and device in the world identifies itself, and keeping that list current is a full time job. Reusing it unchanged means a Ruby application gets the same coverage as the Matomo tool, and a bug fixed in one place fixes it in both.
The project is funded by the owners of Podigee, the podcast hosting, analytics and monetization SaaS for podcasters, and is actively maintained by that development team. The default branch is develop, the last push was on 2026-09-06, and the repository publishes no GitHub releases.
What it detects, and what it returns when it cannot
The detection surface is broad. DeviceDetector will parse any user agent and detect the browser, the operating system, the device used, with desktop, tablet, mobile, tv, cars and console named as examples, plus the brand and model. The README says it detects thousands of user agent strings, even from rare and obscure browsers and devices.
The API is a single object with readers for each attribute:
user_agent = 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36'
client = DeviceDetector.new(user_agent)
client.name # => 'Chrome'
client.full_version # => '30.0.1599.69'
client.os_name # => 'Windows'
client.os_full_version # => '8'
# For many devices, you can also query the device name (usually the model name)
client.device_name # => 'iPhone 5'
# Device types can be one of the following: desktop, smartphone, tablet,
# feature phone, console, tv, car browser, smart display, camera,
# portable media player, phablet, smart speaker, wearable, peripheral
client.device_type # => 'smartphone'The set of device types is worth keeping in front of you, because it is the enum your product logic will branch on: desktop, smartphone, tablet, feature phone, console, tv, car browser, smart display, camera, portable media player, phablet, smart speaker, wearable and peripheral.
The failure behaviour is explicit. DeviceDetector will return nil on all attributes if the user agent is unknown, and there is a separate predicate to test for that case:
client.known? # => will return false if user_agent is unknownSo an unrecognised client is not an exception, it is a nil object and a false answer. Code that reads client.os_name without checking known? will get nil rather than a wrong guess, which is a better failure mode, but it is still a failure mode you have to handle.
Client hints beat a user agent that lies
The optional feature worth knowing about is the use of client hint headers, which the README describes as optionally using the content of Sec-CH-UA stored in the headers to improve the accuracy of the detection.
The example shows why. A user agent string that claims Chrome can be paired with a Sec-CH-UA header that names a different browser, and the header wins:
user_agent = 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36'
headers = {"Sec-CH-UA"=>'"Chromium";v="106", "Brave";v="106", "Not;A=Brand";v="99"'}
client = DeviceDetector.new(user_agent, headers)
client.name # => 'Brave'The same is true of http-x-requested-with and x-requested-with, and there the example is a mobile browser that changes the detection completely:
user_agent = 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36'
headers = {"http-x-requested-with"=>"org.mozilla.focus"}
client = DeviceDetector.new(user_agent, headers)
client.name # => 'Firefox Focus'Without the header, that same user agent string reads as Chrome. The point is that user agent strings are frequently reduced or spoofed, and the hint headers are harder to fake accidentally, so when a client sends them the detection improves materially.
The headers are optional in the constructor, so nothing changes for a caller that does not have them, and the detection falls back to the string alone.
The memory cache is 5,000 entries unless you change it
DeviceDetector is optimized for speed of detection, by providing optimized code and in-memory caching, and the cache is the part you configure.
By default the library caches up to 5,000 user agent strings to boost parsing performance, and you can tune the number of keys that get saved. The README is explicit about the ordering requirement: you have to call this code before you initialize the Detector.
DeviceDetector.configure do |config|
config.max_cache_keys = 5_000 # increment this if you have enough RAM, proceed with care
endThe comment is the guidance. The default of 5,000 is sized for a typical process, and raising it is a memory decision, not a performance decision you can make blindly. The README also says that if you have a Rails application you can create an initializer, for example config/initializers/device_detector.rb, which is the natural place for the configuration call given the ordering rule.
That ordering is the one sharp edge in the whole library. A configuration block that runs after the first DeviceDetector.new has no effect on what has already been parsed, and it fails silently rather than raising.
The cache is per process, which is worth remembering when you read the benchmark numbers, since a benchmark that repeats the same string measures the cache rather than the parser for those entries.
The published benchmark compares three Ruby parsers
The README includes a benchmark section, and it is worth reading carefully because it documents both the method and the versions.
The method: almost 200,000 non-unique user agent strings were measured, and the speed of DeviceDetector was compared with the two most popular user agent parsers in the Ruby community, Browser and UserAgent. The harness is included, so the measurement can be reproduced rather than taken on trust.
require 'device_detector'
require 'browser'
require 'user_agent'
require 'benchmark'
user_agent_strings = File.read('./tmp/user_agent_strings.txt').split("\n")
## Benchmarks
Benchmark.bm(15) do |x|
x.report('device_detector') {
user_agent_strings.each { |uas| DeviceDetector.new(uas).name }
}
x.report('browser') {
user_agent_strings.each { |uas| Browser.new(ua: uas).name }
}
x.report('useragent') {
user_agent_strings.each { |uas| UserAgent.parse(uas).browser }
}
endThe test machine is specified too: a MacBook Pro 15 inch, Late 2013, with a 2.6 GHz Intel Core i7 and 16 GB of 1600 MHz DDR3. The gem versions are DeviceDetector 0.5.1, Browser 0.8.0 and UserAgent 0.13.1.
The results section reports three runs, and in each of them device_detector completes the set in roughly one second while the two alternatives take three and a half to three and a half and a half seconds of real time.
Two cautions on reading these numbers. The benchmark was run on a 2013 laptop with a gem version of 0.5.1, which is several releases behind the current 0.x line, and the strings are non-unique, which means the memory cache is doing part of the work in every run.
That said, the direction of the result is what you would expect from a library that ships a compiled-down matching strategy against a large regex set, and the README is transparent about the machine and the versions, which is more than most benchmark sections bother to be.
Where the repository keeps its data, and where it is wrong for you
The top level listing shows how the project is organised, and it is short: a lib directory for the Ruby code, a regexes directory, a spec directory, the gemspec, a Gemfile, a Rakefile, a .rubocop.yml, a .ruby-version, a CHANGELOG.md, a LICENSE.txt and a .github directory.
The regexes directory is the important one. It is where the upstream YAML regular expressions live, and it is the part of the repository that changes when Matomo updates its data. Everything else is the Ruby adaptation around it.
That layout also explains the limitations, and they are worth stating plainly.
First, this is a guess. A user agent string is supplied by the client, and a determined client can claim to be anything. The library makes an informed inference, and the README's list of detected clients, bots and devices, which was updated on 2024-06-27 and runs to hundreds of names from 2GDPR to ClaudeBot, is a measure of coverage rather than of accuracy.
Second, the data is only as fresh as the upstream update, and the port depends on a pull request crossing between the two projects. If your product's traffic depends on recognising a browser released last week, the lag between Matomo's update and this port's release is the gap you have to plan for.
Third, a nil result is a normal outcome, not an exception, and code that treats device_type as always present will break on traffic the database does not cover.
Fourth, the cache is per process and unbounded by default in the sense that 5,000 is a lot of memory for a small container, and the configuration has to happen before the first detection.
For an application that needs to segment analytics, choose a layout, or block a client, that is a good trade. For an application that needs to know what the user is doing, it is the wrong instrument.
Editorial conclusion
Ruby applications that need to know what kind of client is connecting should adopt device_detector, because it ships the Matomo regex database unchanged, which means coverage of obscure browsers and devices is far wider than a hand written parser, and it accepts client hint headers to resolve the cases where the user agent string lies. It is the wrong tool when you need certainty rather than a guess, because detection is inference from a string the client controls, and it is the wrong choice if you cannot take the upstream regex updates. Before adopting, check the device types your product cares about against the list the README enumerates, and set max_cache_keys deliberately rather than leaving the default of 5,000 entries in place.
Frequently asked questions
How do I install device_detector?
Add gem 'device_detector' to your application's Gemfile and run bundle, or install it with gem install device_detector. The project is an adaptation for Ruby of the Universal Device Detection library maintained by Matomo Analytics.
What happens when device_detector does not recognise a user agent?
It returns nil on all attributes, and the known? predicate returns false. The README documents this as the behaviour for an unknown user agent, so callers need to check for it rather than assuming every attribute is populated.
How do I use client hint headers with device_detector?
Pass the headers as the second argument to DeviceDetector.new, for example a Sec-CH-UA header, and the library uses them to improve detection accuracy. The README also shows the same approach with http-x-requested-with and x-requested-with headers, which can change the detected client entirely.
How do I configure the device_detector cache?
Call DeviceDetector.configure and set config.max_cache_keys, which defaults to 5,000. The README stresses that this code has to run before you initialize the Detector, and in a Rails application the natural place for it is a file such as config/initializers/device_detector.rb.
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/podigee-device-detector)Was this useful?