<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ido</title>
    <description>The latest articles on DEV Community by Ido (@dmw2007).</description>
    <link>https://dev.to/dmw2007</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1370742%2Fde1c0b03-2717-49ae-b320-c5cd1a1db2be.png</url>
      <title>DEV Community: Ido</title>
      <link>https://dev.to/dmw2007</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmw2007"/>
    <language>en</language>
    <item>
      <title>Built a User-Agent Parser for Kotlin Multiplatform</title>
      <dc:creator>Ido</dc:creator>
      <pubDate>Wed, 09 Sep 2026 05:43:40 +0000</pubDate>
      <link>https://dev.to/dmw2007/built-a-user-agent-parser-for-kotlin-multiplatform-42mf</link>
      <guid>https://dev.to/dmw2007/built-a-user-agent-parser-for-kotlin-multiplatform-42mf</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fup455w7e2digmirxkhqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fup455w7e2digmirxkhqn.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://user-agent.lempert.site" rel="noopener noreferrer"&gt;Docs&lt;/a&gt; | &lt;a href="https://github.com/ido-lempert/kmp-user-agent" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I needed a User-Agent parser.&lt;/p&gt;

&lt;p&gt;That sounds like a pretty small problem.&lt;/p&gt;

&lt;p&gt;Take a string, find Chrome or Safari, figure out the OS, return an object. Done.&lt;/p&gt;

&lt;p&gt;Except that once you start supporting more than one platform, the problem gets annoying pretty quickly.&lt;/p&gt;

&lt;p&gt;I ended up building &lt;a href="https://user-agent.lempert.site/" rel="noopener noreferrer"&gt;kmp-user-agent&lt;/a&gt; to solve that problem with one shared implementation for Kotlin Multiplatform.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem I was trying to solve
&lt;/h2&gt;

&lt;p&gt;User-Agent parsing is usually implemented as a collection of string checks and regular expressions.&lt;/p&gt;

&lt;p&gt;Something along these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Chrome"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Chrome&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Android"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Android&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works until it doesn't.&lt;/p&gt;

&lt;p&gt;Browsers contain tokens from other browsers for compatibility reasons. Mobile User-Agents have their own variations. WebViews are another case. Then there are crawlers and other automated clients.&lt;/p&gt;

&lt;p&gt;And if you have the same logic in JavaScript, Android and iOS, you now have the same problem in three places.&lt;/p&gt;

&lt;p&gt;I didn't want that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why KMP made sense here
&lt;/h2&gt;

&lt;p&gt;The interesting thing about User-Agent parsing is that most of the logic is completely platform independent.&lt;/p&gt;

&lt;p&gt;The input is a string.&lt;/p&gt;

&lt;p&gt;The output is structured data.&lt;/p&gt;

&lt;p&gt;There isn't much platform-specific work involved in deciding whether a User-Agent represents Chrome running on Android.&lt;/p&gt;

&lt;p&gt;So this felt like a good fit for Kotlin Multiplatform.&lt;/p&gt;

&lt;p&gt;The goal became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              User-Agent
                   |
                   v
          Shared KMP parser
                   |
        +----------+----------+
        |          |          |
      Android     iOS       JVM / JS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One set of detection rules.&lt;/p&gt;

&lt;p&gt;One model.&lt;/p&gt;

&lt;p&gt;Different platform bindings.&lt;/p&gt;

&lt;p&gt;The current library supports Browser/Node.js, React Native, Android, iOS and JVM. The React Native implementation uses the same JS build, with Metro and Hermes tested separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the parser return?
&lt;/h2&gt;

&lt;p&gt;I didn't want consumers to work with a bunch of strings.&lt;/p&gt;

&lt;p&gt;The parser returns a structured &lt;code&gt;UserAgentInfo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The model currently covers things such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
Engine
Operating System
Device
Bot
AI Agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So application code can work with the result instead of knowing anything about the actual User-Agent format.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;info&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UserAgentParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operatingSystem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part for me is that the application shouldn't need to know why a particular regular expression matched.&lt;/p&gt;

&lt;p&gt;That's the parser's job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detection packs
&lt;/h2&gt;

&lt;p&gt;One design decision I'm particularly happy with is the use of detection packs.&lt;/p&gt;

&lt;p&gt;Instead of having one huge parser where everything is enabled by default, the detection categories can be composed.&lt;/p&gt;

&lt;p&gt;For example, an application might only need browser and OS detection.&lt;/p&gt;

&lt;p&gt;Another one might also need device detection.&lt;/p&gt;

&lt;p&gt;The basic idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;UserAgentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;packs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;UserAgentBrowserTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;UserAgentOperatingSystemTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;UserAgentDeviceTypes&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also makes the JavaScript build more interesting because unused packs can be removed by the bundler.&lt;/p&gt;

&lt;p&gt;I tested this with webpack and esbuild.&lt;/p&gt;

&lt;p&gt;There is one caveat: React Native's Metro doesn't provide the same tree-shaking behavior, so I don't consider the JS behavior identical across every bundler.&lt;/p&gt;

&lt;p&gt;I prefer documenting limitations like this rather than pretending the optimization works everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing isn't the only thing
&lt;/h2&gt;

&lt;p&gt;While building the parser, I realized that generating User-Agent strings was useful too.&lt;/p&gt;

&lt;p&gt;It's handy for tests and for reproducing requests with different client characteristics.&lt;/p&gt;

&lt;p&gt;So the library also has a &lt;code&gt;UserAgentGenerator&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-Agent string
       |
       v
     Parser
       |
       v
 UserAgentInfo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the other direction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UserAgentInfo / filters
       |
       v
   Generator
       |
       v
User-Agent string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can select things like browser, engine, OS and device and generate a plausible User-Agent.&lt;/p&gt;

&lt;p&gt;It's also useful when testing code that has different behavior depending on the detected client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending the parser
&lt;/h2&gt;

&lt;p&gt;There is another problem with User-Agent libraries.&lt;/p&gt;

&lt;p&gt;No matter how many signatures you add, somebody will eventually have a User-Agent that isn't recognized.&lt;/p&gt;

&lt;p&gt;I didn't want the solution to be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fork the repository and maintain your own version.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The library therefore allows custom &lt;code&gt;UserAgentTypePack&lt;/code&gt; implementations.&lt;/p&gt;

&lt;p&gt;That means you can add your own detection rules or override existing results without changing the core parser.&lt;/p&gt;

&lt;p&gt;There is also a &lt;code&gt;custom&lt;/code&gt; map for information that doesn't fit into the predefined model.&lt;/p&gt;

&lt;p&gt;This makes the parser useful for applications that have their own internal clients as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript is a first-class target
&lt;/h2&gt;

&lt;p&gt;Since User-Agent parsing is particularly common on the web, I wanted the JavaScript package to be a real part of the project rather than an afterthought.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserAgentParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operatingSystem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package is published to npm and can be used from browser and Node.js applications.&lt;/p&gt;

&lt;p&gt;The same shared implementation is also used by the other targets.&lt;/p&gt;

&lt;p&gt;That's the part I like most about the project.&lt;/p&gt;

&lt;p&gt;If I find a problem in the detection rules, I'm fixing the shared implementation rather than maintaining separate JavaScript, Android and iOS parsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about bots and AI crawlers?
&lt;/h2&gt;

&lt;p&gt;This is an area I'm still expanding.&lt;/p&gt;

&lt;p&gt;A User-Agent doesn't necessarily represent a person using a browser.&lt;/p&gt;

&lt;p&gt;There are search crawlers, link preview bots, application clients and AI-related crawlers.&lt;/p&gt;

&lt;p&gt;I wanted these to be represented separately rather than simply returning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isBrowser = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library therefore has separate concepts for bots and AI agents.&lt;/p&gt;

&lt;p&gt;The detection packs include signatures for known automated clients, and I'm treating this as an evolving part of the project because these identifiers can change.&lt;/p&gt;

&lt;p&gt;This is also why I don't think User-Agent detection should be treated as a security boundary.&lt;/p&gt;

&lt;p&gt;It's useful for classification.&lt;/p&gt;

&lt;p&gt;It isn't proof of identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  One small project, a few interesting problems
&lt;/h2&gt;

&lt;p&gt;The actual parsing code isn't particularly glamorous.&lt;/p&gt;

&lt;p&gt;The interesting engineering problems were elsewhere:&lt;/p&gt;

&lt;p&gt;How should the model look?&lt;/p&gt;

&lt;p&gt;How do you keep behavior consistent across platforms?&lt;/p&gt;

&lt;p&gt;How do you make detection extensible?&lt;/p&gt;

&lt;p&gt;How do you avoid shipping unnecessary detection logic to a JavaScript application?&lt;/p&gt;

&lt;p&gt;How do you test the same behavior across different KMP targets?&lt;/p&gt;

&lt;p&gt;And perhaps most importantly:&lt;/p&gt;

&lt;p&gt;How much complexity should the library expose to its users?&lt;/p&gt;

&lt;p&gt;I tried to keep the public API relatively small and push the ugly parts into the implementation.&lt;/p&gt;

&lt;p&gt;That's usually a good trade for a utility library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it is today
&lt;/h2&gt;

&lt;p&gt;The project is open source and MIT licensed.&lt;/p&gt;

&lt;p&gt;The currently published package is &lt;code&gt;0.2.0&lt;/code&gt;, with the shared implementation available across the supported platforms.&lt;/p&gt;

&lt;p&gt;There are also newer detection packs in development, including bot and AI-agent detection, so not everything visible in the source/docs necessarily means it is already part of the published package.&lt;/p&gt;

&lt;p&gt;The live documentation has examples for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser / Node.js&lt;/li&gt;
&lt;li&gt;React Native&lt;/li&gt;
&lt;li&gt;Android&lt;/li&gt;
&lt;li&gt;iOS&lt;/li&gt;
&lt;li&gt;JVM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can try the parser directly in the documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://user-agent.lempert.site/" rel="noopener noreferrer"&gt;https://user-agent.lempert.site/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;This started as one of those utilities that seemed too small to deserve a library.&lt;/p&gt;

&lt;p&gt;Then I realized that was exactly why I wanted to build it.&lt;/p&gt;

&lt;p&gt;Small infrastructure problems tend to get copied into projects.&lt;/p&gt;

&lt;p&gt;Then they get slightly modified.&lt;/p&gt;

&lt;p&gt;Then somebody fixes a bug in one copy but not the others.&lt;/p&gt;

&lt;p&gt;Then six months later nobody remembers why one regular expression looks completely ridiculous.&lt;/p&gt;

&lt;p&gt;I'd rather have one implementation that can be reused.&lt;/p&gt;

&lt;p&gt;That's the main idea behind &lt;code&gt;kmp-user-agent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Not trying to reinvent User-Agent parsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just trying to make it something I don't have to implement again.
&lt;/h2&gt;

&lt;p&gt;If you work with Kotlin Multiplatform and have a similar small utility problem, I'd be interested to hear what you've ended up sharing between platforms.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>npm</category>
      <category>ios</category>
      <category>android</category>
    </item>
    <item>
      <title>Just published my first Kotlin Multiplatform open-source project.

It’s a small User-Agent parser/generator that detects browsers, OS, bots, AI agents and more.

Would love feedback

https://user-agent.lempert.site</title>
      <dc:creator>Ido</dc:creator>
      <pubDate>Tue, 08 Sep 2026 19:36:36 +0000</pubDate>
      <link>https://dev.to/dmw2007/just-published-my-first-kotlin-multiplatform-open-source-project-its-a-small-user-agent-3ie8</link>
      <guid>https://dev.to/dmw2007/just-published-my-first-kotlin-multiplatform-open-source-project-its-a-small-user-agent-3ie8</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://user-agent.lempert.site" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;user-agent.lempert.site&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>kotlin</category>
      <category>opensource</category>
      <category>programming</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
