publicsuffix-ruby: parsing a domain into TLD, SLD and TRD the way browsers do
Domain name parser for Ruby based on the Public Suffix List.
At a glance
- What is it?
- A Ruby gem that implements the Public Suffix List, so a name like www.google.co.uk is split the way a browser would split it, with a bundled list and no network calls. It handles private domains, fully qualified names, validation and custom rules, and it requires Ruby 3.2 or later.
- Who is it for?
- Adopt publicsuffix-ruby if you write Ruby and need to know where the registrable part of a domain ends, which is the difference between a public suffix such as co.uk and everything above it. Skip it if your inputs are always simple second level names on a single known TLD, since a split on the last dot is then enough.
- 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 received new commits within the last day.
- 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
Why the public suffix boundary is not the last dot
The readme explains the problem this library solves better than most documentation of this kind does, and the explanation is a security argument.
Previously, browsers used an algorithm which basically only denied setting wide-ranging cookies for top-level domains with no dots, for example com or org. However, this did not work for top-level domains where only third-level registrations are allowed, for example co.uk. In these cases, websites could set a cookie for co.uk which will be passed onto every website registered under co.uk.
The consequence was that a site other than the one setting the cookie could read it, and therefore potentially extract sensitive information. Because there is no algorithmic method of finding the highest level at which a domain may be registered for a particular top-level domain, since the policies differ with each registry, the only method is to create a list of all top-level domains and the level at which domains can be registered.
That is what the Public Suffix List is. It is a cross-venture initiative, originally from the Mozilla Project, maintained as a community resource, created to meet the needs of browser manufacturers. A public suffix is one under which Internet users can directly register names, and the examples given are .com, .co.uk and pvt.k12.wy.us.
So the rule this gem implements is that a cookie set on co.uk is a cross-site cookie, and to know whether a domain is safe to set a cookie on, you need the list rather than a heuristic. The readme also points out that the list can potentially be used for other applications where registry controlled and privately controlled parts of a domain name need to be known, for example when grouping by top-level domains.
The four-part split, and what each piece means
The library's terminology is worth learning because it is the terminology the domain world actually uses.
TLD is the Top-Level Domain, the last segment of a domain name, so in mozilla.org the .org portion is the TLD. SLD is the Second-Level Domain, a domain directly below a top-level domain, so in https://www.mozilla.org/en-US/ the mozilla is the second-level domain of the .org TLD. TRD is the Third-Level Domain, also known as a subdomain, the part of the domain before the SLD or root domain, so www in that same example. And FQDN is the Fully Qualified Domain Name, a complete domain name that includes the hostname, domain and top-level domain, ending with a trailing dot, in the format [hostname].[domain].[tld].
The parse API returns an object with a method for each of those, which is what makes the library useful rather than a string splitter:
domain = PublicSuffix.parse("google.com")
# => #<PublicSuffix::Domain>
domain.tld
# => "com"
domain.sld
# => "google"
domain.trd
# => nil
domain.domain
# => "google.com"
domain.subdomain
# => nilAnd with a subdomain present, the same object reports the third level:
domain = PublicSuffix.parse("www.google.com")
domain.trd
# => "www"
domain.domain
# => "google.com"
domain.subdomain
# => "www.google.com"The distinction between domain and subdomain is the one that catches people. The domain is the registrable name, google.com, and the subdomain is everything before it, www.google.com. When you have several subdomain levels, trd is only the third level and subdomain covers the rest.
Installing it, and the Ruby version floor
The install is a standard gem, and the requirement is worth noting before you plan around it.
PublicSuffix requires Ruby >= 3.2. For older versions of Ruby, use a previous release. That is a recent floor for a gem that has been around since 2010, based on the blog post linked in the readme about introducing the Public Suffix List library for Ruby, and it means an application still on an older Ruby has to track which release to pin.
The manual install is:
gem install public_suffixOr you can use Bundler and define it as a dependency in your Gemfile:
gem 'public_suffix'The gem is published as public_suffix on RubyGems, and the API documentation is at rubydoc.info/gems/public_suffix. The library documentation is auto-generated from the readme and the source code, which means the readme you are reading is the documentation, and the rubydoc site is the same content with signatures filled in.
The top level of the repository reflects a mature gem. There is a .rubocop.yml with an _opinionated variant and a _todo variant, a .yardopts for the documentation generator, a Gemfile, a Rakefile, a public_suffix.gemspec, a bin directory, a data directory that holds the list, lib for the code, and a test directory. There are also RELEASING.md and SECURITY.md, which are the two files you would want to find in a gem you are thinking of depending on for security relevant parsing.
The repository is not archived and the last push was on 2026-10-19, with a continuous integration workflow for tests. There are no GitHub releases, since a Ruby gem is released through RubyGems.
Private domains change the answer, and there is a performance argument
The interesting configuration decision in this library is private domains, which are the non-ICANN suffixes that registries operate, such as blogspot.com and github.io.
The library supports toggling private domain handling, and the difference is visible in the result:
# Extract a domain including private domains (by default)
PublicSuffix.domain("something.blogspot.com")
# => "something.blogspot.com"
# Extract a domain excluding private domains
PublicSuffix.domain("something.blogspot.com", ignore_private: true)
# => "blogspot.com"The same flag works for parse and for valid?, which matters because the two are often used together in a request pipeline.
But the more interesting part is the performance note. If you don't care about private domains at all, it's more efficient to exclude them when the list is parsed:
# Disable support for private TLDs
PublicSuffix::List.default = PublicSuffix::List.parse(File.read(PublicSuffix::List::DEFAULT_LIST_PATH), private_domains: false)
# => "blogspot.com"
PublicSuffix.domain("something.blogspot.com")
# => "blogspot.com"So there are two ways to do the same thing, and they are not equivalent in cost. Passing ignore_private on every call leaves the private rules in the parsed list and discards the match afterwards, whereas reparsing the default list without private domains removes those rules from the data structure once, at the cost of rebuilding the list. For an application that parses a domain on every request, that difference is the difference between a per-call check and a per-process rebuild.
The list is bundled with the gem, and the readme is explicit that PublicSuffix comes with a bundled list and that it does not make any HTTP requests to parse or validate a domain. For a library that runs on every URL your application sees, having no network dependency is not a convenience, it is the reason you can use it at all in a hot path.
Validation, fully qualified names and custom rules
The remaining API surface is small and worth going through, because each piece answers a question that comes up in real code.
Validation is a predicate, and its default behaviour is the one that surprises people:
PublicSuffix.valid?("google.com")
# => true
PublicSuffix.valid?("www.google.com")
# => true
# Explicitly forbidden, it is listed as a private domain
PublicSuffix.valid?("blogspot.com")
# => false
# Unknown/not-listed TLD domains are valid by default
PublicSuffix.valid?("example.tldnotlisted")
# => trueThe default rule is permissive for unknown TLDs, which is a deliberate choice rather than an oversight, and the strict form opts out of it by passing default_rule: nil:
PublicSuffix.valid?("example.tldnotlisted", default_rule: nil)
# => falseFully qualified domain names are recognised automatically, meaning a trailing dot is handled rather than rejected:
# Parse a standard domain name
PublicSuffix.domain("www.google.com")
# => "google.com"
# Parse a fully qualified domain name
PublicSuffix.domain("www.google.com.")
# => "google.com"And you can extend the list at runtime, which is what you need when your organisation operates a private suffix:
PublicSuffix::List.default << PublicSuffix::Rule.factory('onmicrosoft.com')One thing the readme does not cover is what happens to the bundled list over time, which is the question to ask before depending on it. The gem version and the list version inside it are separate things, and a security relevant decision such as which domains count as public suffixes changes as registries change their policies. The readme links the changelog and the source, and that is where the answer is.
Where this library is the wrong choice
There are clear cases where the public suffix approach is more than you need, and one where it is not enough.
The first is when your inputs are simple. If every domain you handle is a second level name on a known TLD, splitting on the last dot is correct, and a gem that loads a list of rules to answer that question is a dependency you did not need. The value of the list appears when the input can be anything a user typed.
The second is when the domain is not the thing you are checking. A library that parses domain names does not resolve them, does not check that a host exists, and does not tell you whether a URL is safe to fetch. If your problem is SSRF protection, you need to resolve the name and check the address, and the suffix boundary is only one part of that answer.
The third is the private domain decision, and it cuts both ways. Including private domains means a site on a shared hosting suffix is treated as sharing a registrable boundary, which is the safe default for cookie and origin checks. Excluding them makes something.blogspot.com a subdomain of blogspot.com, which is right for grouping by operator and wrong for isolation. The performance note in the readme pushes towards excluding them, and the security note pushes towards including them, and you have to pick based on what the library is for in your application.
The fourth is a stale list. The bundled list is a snapshot at the version of the gem you installed. Public suffix policy changes, and a library that never makes network requests will never learn about them without a gem upgrade. For cookie scope decisions that is fine, because the list only grows and the effect of a missing rule is a stricter cookie scope. For logic that depends on a specific new suffix existing, it is a real limitation.
And the fifth is the Ruby version floor of 3.2, which is high for a library of this age. A project on an older Ruby is pinned to an older release of the gem, and older releases of the gem are not getting the fixes that the current one has.
Against a regular expression, and against the browser's own list
Two comparisons make the value of the library concrete.
The first is against a regular expression, and the readme's own section title is Why use the public suffix list instead of regular expressions. The argument is not that regular expressions cannot express co.uk, it is that they cannot express the general rule, because the registry policies differ for every suffix. You can hard code a hundred exceptions and you will still miss the hundred and first, and a new registry policy will not be in your list. The list is the only method that keeps up, and the readme says so directly: since there is no algorithmic method of finding the highest level at which a domain may be registered for a particular top-level domain, the policies differ with each registry, the only method is to create a list.
The second is against using the browser's own list, which is the obvious alternative since every browser has one. The difference is that a browser will not tell you what the public suffix is. A browser applies the list internally to decide cookie scope and to decide what counts as a site for permissions, but a server running your application has no such facility. The gem is the way to get that same judgement in your own code, with the same rules the browser uses, which is what makes an origin check in your server agree with the browser's.
So the honest framing is that this is a small, focused library that solves one well-defined problem: where does the registrable part of a domain end. If that is your problem, the gem is the shortest correct answer, and the alternative is either a growing pile of regular expression exceptions or a homegrown copy of the list that will drift.
Editorial conclusion
Adopt publicsuffix-ruby if you write Ruby and need to know where the registrable part of a domain ends, which is the difference between a public suffix such as co.uk and everything above it. Skip it if your inputs are always simple second level names on a single known TLD, since a split on the last dot is then enough. Verify first whether your use case needs private domains treated as suffixes, because that decision changes both the parse result and the size of the bundled list, and check that the Ruby version in your application meets the 3.2 requirement or pin an earlier release.
Frequently asked questions
What does publicsuffix-ruby do?
It is a Ruby domain name parser based on the Public Suffix List, so a name is split into its TLD, SLD, TRD and subdomain the way a browser would split it. It also provides a valid? predicate and lets you extend the list with custom rules.
How do I install publicsuffix-ruby?
Run gem install public_suffix, or add gem 'public_suffix' to your Gemfile and use Bundler. The gem requires Ruby >= 3.2, and for older versions of Ruby you should use a previous release.
What is the difference between SLD, TRD and subdomain in publicsuffix-ruby?
SLD is the second-level domain directly below the TLD, TRD is the third-level domain, also known as a subdomain, and the subdomain attribute covers everything before the registrable domain. For www.google.com the tld is com, the sld is google, the trd is www and the domain is google.com.
How do I handle private domains such as blogspot.com in publicsuffix-ruby?
Private domains are included by default, so PublicSuffix.domain("something.blogspot.com") returns "something.blogspot.com". Pass ignore_private: true to exclude them, or, for better performance, parse the default list with private_domains: false and assign it to PublicSuffix::List.default.
Does publicsuffix-ruby make network requests?
No. PublicSuffix comes with a bundled list and does not make any HTTP requests to parse or validate a domain, which is what makes it usable on a hot path.
How current is publicsuffix-ruby and what licence does it use?
It is MIT licensed, with the licence in LICENSE.txt. The repository is not archived, the last push was on 2026-10-19, and there are no GitHub releases because a Ruby gem is released through RubyGems rather than through GitHub release artefacts.
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/weppos-publicsuffix-ruby)Was this useful?