Library / SDK
john-kurkowski/tldextract avatar
john-kurkowski/tldextract

tldextract: subdomain, domain and public suffix separation using the Public Suffix List

Accurately separates a URL’s subdomain, domain, and public suffix, using the Public Suffix List (PSL).

2,027 stars223 forksPythonBSD-3-Clause

At a glance

What is it?
tldextract splits a URL into subdomain, domain and suffix using the community-maintained Public Suffix List, handling multi-label suffixes like co.uk that naive dot-splitting gets wrong. It caches the list locally, returns structured results with computed FQDN and top-domain properties, and raises an explicit IDNA normalization warning for security-sensitive uses.
Who is it for?
tldextract suits any Python code that needs to correctly separate a domain from its suffix across the full variety of real-world TLDs and second-level domains. In production, disable HTTP fetching with `suffix_list_urls=()` to avoid network calls at startup; switching to an empty URL list bypasses any cache built with the default URLs, so the bundled snapshot is used instead.
Can I use it commercially?
Yes. BSD-3-Clause 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 7 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Dot-splitting on periods returns wrong results for co.uk and similar multi-label suffixes

A common URL parsing approach splits on dots and takes the last label as the suffix. For domains like `forums.bbc.co.uk`, that returns uk, not `co.uk`. For `forums.news.cnn.com` the suffix is com, which happens to be correct, but the logic breaks for any country-code second-level domain registered with a public authority.

tldextract solves this by consulting the Public Suffix List, a community-maintained catalog of domain suffixes published at publicsuffix.org. The PSL entry for `co.uk` tells tldextract that both labels together form the suffix, so the domain is correctly identified as bbc and the subdomain as forums.

The library accepts any string, including partial URLs and non-URL strings, prioritizing ease of use over strict validation. That leniency is the documented design choice; callers who want strict validation are directed to use `extract_urllib` with a URL pre-split by `urlsplit` from `urllib.parse`.

ExtractResult carries subdomain, domain, suffix and is_private, plus fqdn and top_domain_under_public_suffix

The return value of `tldextract.extract` is an ExtractResult namedtuple-like object. Its four primary fields are `subdomain`, `domain`, `suffix` and `is_private`. Two computed properties are also available: `fqdn`, which reconstructs the fully qualified domain name, and `top_domain_under_public_suffix`, which joins domain and suffix.

The Quick Start shows these three together:

python
>>> ext = tldextract.extract('http://forums.bbc.co.uk')
>>> ext.domain
'bbc'
>>> ext.top_domain_under_public_suffix
'bbc.co.uk'
>>> ext.fqdn
'forums.bbc.co.uk'

`fqdn` and `top_domain_under_public_suffix` are not described in section headings in the README, but both appear in the Quick Start code block. `top_domain_under_public_suffix` combines domain and suffix into a string that is useful when you need the registrable domain without the subdomain.

The `is_private` field is not a property of the domain in the PSL; its meaning depends on whether the extraction treated private suffixes as suffixes. That behavior is explained in a later section.

Installing via pip and running a first extraction from Python or the command line

Install tldextract with:

bash
pip install tldextract

In Python, import and call the module-level extract function:

python
>>> import tldextract
>>> tldextract.extract('http://forums.bbc.co.uk/')
ExtractResult(subdomain='forums', domain='bbc', suffix='co.uk', is_private=False)
>>> tldextract.extract('http://forums.news.cnn.com/')
ExtractResult(subdomain='forums.news', domain='cnn', suffix='com', is_private=False)

The same extraction is available from the command line after installation:

zsh
$ tldextract http://forums.bbc.co.uk
forums bbc co.uk

The CLI also exposes `tldextract --update` to refresh the cached suffix list and `tldextract --help` for all options. On first use, tldextract fetches the latest PSL over HTTP and caches it in `$HOME/.cache/python-tldextract`. Subsequent calls use the cache until you run `--update` or delete that directory.

tldextract 5.4.0 requires Python 3.10 or later, as stated in pyproject.toml. The classifier list runs from Python 3.10 through 3.14.

Disabling HTTP suffix list fetching for production deployments

A production service that calls tldextract at startup should not depend on an outbound HTTP request to publicsuffix.org. Two approaches disable the network call.

In code, pass an empty tuple for `suffix_list_urls`:

python
no_fetch_extract = tldextract.TLDExtract(suffix_list_urls=())
no_fetch_extract("http://www.google.com")

Or set an environment variable before importing tldextract:

zsh
export TLDEXTRACT_PUBLIC_SUFFIX_LIST_URLS=""

With fetching disabled and no matching cache entry, tldextract falls back to its bundled snapshot. The fetch timeout can also be reduced to limit startup delay for those who keep fetching enabled:

zsh
export TLDEXTRACT_DEFAULT_FETCH_TIMEOUT="1.2"

A single value applies separately to the connection phase and the response-read phase of each remote request. For the fetch timeout in code, use `cache_fetch_timeout=1.2` when constructing a TLDExtract instance. The library depends on requests (>=2.1.0) for HTTP work and filelock (>=3.0.8) for cache file locking; disabling fetching removes the HTTP requests but local cache reads still use filelock.

is_private reflects how tldextract treated the domain, not whether it is in the PSL private section

The PSL contains both public suffixes (.com, .co.uk) and private suffixes operated by companies for customer subdomains (blogspot.com, github.io). By default, tldextract treats private suffixes as regular domains rather than as suffixes.

For `waiterrant.blogspot.com` under the default configuration:

python
>>> tldextract.extract('waiterrant.blogspot.com')
ExtractResult(subdomain='waiterrant', domain='blogspot', suffix='com', is_private=False)

With `include_psl_private_domains=True`, the result is different:

python
extract = tldextract.TLDExtract(include_psl_private_domains=True)
extract("waiterrant.blogspot.com")
# ExtractResult(subdomain='', domain='waiterrant', suffix='blogspot.com', is_private=True)

The `is_private=False` in the first example does not mean blogspot.com is absent from the PSL's private section. It means tldextract did not treat it as a suffix. `is_private` in any result indicates how that specific extraction classified the suffix, which depends on the instance's configuration. Code that reads `is_private` expecting it to reliably identify all PSL-private domains will miss them in the default configuration.

IDNA hostname normalization must happen before using extracted values in security decisions

tldextract identifies public suffix boundaries but does not validate or canonicalize hostnames. Returned strings retain the input's casing and its Unicode or ASCII-compatible encoding. Two hostnames that refer to the same server can produce textually different results even when tldextract identifies the same suffix boundary.

tldextract's own documentation names this explicitly: before using suffix, fqdn, or joined-domain properties in equality checks, allowlists, blocklists or other security decisions, validate and normalize both hostnames to the same IDNA representation. IDNA defines label equivalence in terms of A-labels, described in RFC 5890, section 2.3.2.4.

For a URL parsing task that feeds extracted domains into a security comparison, that normalization step belongs in the caller's code. tldextract does the boundary identification; it does not enforce a canonical form.

Unlisted labels leave suffix empty, unlike the PSL formal algorithm's implicit star rule

When a hostname's final label is not in the PSL, the formal PSL algorithm applies an implicit star rule and treats the last label as a suffix. tldextract does not do that: for an unlisted final label, the `suffix` field in the result remains empty.

That design choice lets callers tell the difference between a hostname on a known public suffix and one whose TLD is unlisted, which covers unknown, invalid, or internal hostnames. An empty suffix signals that no PSL entry matched, so callers can reject or route the hostname accordingly.

Code that does not check for an empty suffix may mishandle internal hostnames by treating the final label as a valid domain instead of an unrecognized one.

The cache key for tldextract includes the configured URL list. An empty URL list does not reuse a cache entry fetched with the standard PSL URL; if no entry exists for that empty-URL-list key, tldextract uses its bundled snapshot. Switching from default to no-fetch mode in an environment with an existing cache does not recycle that cache.

Editorial conclusion

tldextract suits any Python code that needs to correctly separate a domain from its suffix across the full variety of real-world TLDs and second-level domains. In production, disable HTTP fetching with `suffix_list_urls=()` to avoid network calls at startup; switching to an empty URL list bypasses any cache built with the default URLs, so the bundled snapshot is used instead. Before using extracted suffix, fqdn or top_domain_under_public_suffix values in security decisions such as allowlists or cookie scoping, normalize both hostnames to the same IDNA A-label form as defined in RFC 5890 section 2.3.2.4. For unknown internal hostnames, check that suffix is not empty before acting on the result.

Frequently asked questions

What is tldextract?

tldextract is a Python library that separates a URL into subdomain, domain and public suffix using the Public Suffix List. It handles multi-label suffixes like co.uk and private domains like blogspot.com that simple dot-splitting cannot parse correctly.

How do I install tldextract?

Install it with pip install tldextract. It requires Python 3.10 or later. On first use, it fetches the Public Suffix List and caches it in $HOME/.cache/python-tldextract.

How do I use tldextract without making HTTP requests?

Pass suffix_list_urls=() when constructing a TLDExtract instance, or set the environment variable TLDEXTRACT_PUBLIC_SUFFIX_LIST_URLS to an empty string before importing tldextract. Both approaches disable network requests and fall back to the bundled snapshot.

Does tldextract handle private domains like blogspot.com?

By default, tldextract treats private domains as regular domains, so blogspot.com appears as the domain rather than the suffix. To treat private PSL entries as suffixes, pass include_psl_private_domains=True to TLDExtract.

Is tldextract open source?

Yes. tldextract is distributed under the BSD-3-Clause license, as declared in pyproject.toml.

Official sources

  1. Issues
  2. john-kurkowski/tldextract on GitHub
  3. License: BSD-3-Clause
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/john-kurkowski-tldextract.svg)](https://hysenlabs.com/projects/john-kurkowski-tldextract)