tldextract review
tldextract 5.3.2 separates a hostname into subdomain, registrable domain, and public suffix by consulting the Public Suffix List used for browser cookie boundaries. A dot split gets forums.bbc.co.uk wrong because co.uk is one suffix; tldextract returns forums, bbc, and co.uk. ExtractResult also exposes fqdn, top_domain_under_public_suffix, reverse_domain_name, IP helpers, and an is_private flag. Version 5.3.2 fixes stray dots in reverse_domain_name when the suffix or domain is empty, corrects extract_urllib documentation, and documents IDNA representation and the library's handling of unlisted suffixes. Its default first lookup may fetch and cache the current PSL, which is the main production surprise.
tldextract 5.3.2 installed 8 packages in 0.3 seconds and used 4 MB in our sandbox, with a 0.45-second import and 0 audit findings. It is the right tool for PSL-aware site boundaries once you disable or control the first-run fetch, normalize hostnames, and choose how private suffixes should behave.
We installed it
| Install | ✓ · 0.3s | 8 packages on disk · 4 MB |
| Import | ✓ | import tldextract in 0.45s · pure Python · py.typed · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does tldextract install cleanly?
Yes. In a fresh container with an empty cache, pip install tldextract finished in 0.3s, leaving 8 packages and 4 MB on disk. pip-audit reported no known vulnerabilities.
What does tldextract need to run?
Python >=3.10, and nothing compiled: it is pure Python. In our run import tldextract succeeded in 0.45s, and the package ships py.typed for type checkers.
tldextract or publicsuffix2: which should you use?
publicsuffix2: Choose it for an older PSL interface that works from a packaged list and follows the publicsuffix API. tldextract 5.3.2 installed 8 packages in 0.3 seconds and used 4 MB in our sandbox, with a 0.45-second import and 0 audit findings.
When should you not use tldextract?
A cold process cannot perform network I/O. The default extractor may download the PSL on its first call, and its fetch timeout is unset unless TLDEXTRACT_CACHE_TIMEOUT is configured.
Use it if
- Analytics, rate limiting, or deduplication must group forums.bbc.co.uk with bbc.co.uk while keeping unrelated co.uk registrations separate.
- Security rules need site boundaries based on the same Public Suffix List concept used by browsers for cookie scope.
- Tenant isolation needs private suffixes such as blogspot.com or github.io to count as boundaries through include_psl_private_domains.
- The service can manage a cached PSL snapshot or an intentionally scheduled update from the public list or an internal mirror.
- A cold process cannot perform network I/O. The default extractor may download the PSL on its first call, and its fetch timeout is unset unless TLDEXTRACT_CACHE_TIMEOUT is configured.
- Replicas must produce byte-for-byte deterministic results without shared files. The disk cache does not expire automatically, so instances started at different times may use different PSL revisions.
- The application only needs URI syntax parsing. uritools handles URI components; tldextract deliberately accepts loose text and does not validate a hostname.
- A normalized security identifier is required directly from the library. Returned labels retain input case and Unicode or ACE representation, so callers must validate and canonicalize IDNA themselves.
- Existing code depends on registered_domain. That property is deprecated in the 5.x line in favor of top_domain_under_public_suffix and is scheduled for removal in 6.0.0.
Setup reality
We installed tldextract 5.3.2 in 0.3 seconds in a clean Python 3.12 Bookworm sandbox. The environment contained 8 packages using 4 MB afterward. tldextract declares 4 direct dependencies, is pure Python, requires Python 3.10 or newer, and ships py.typed. import tldextract completed in 0.45 seconds. pip-audit reported 0 known vulnerabilities. The distribution metadata did not provide a license value in our measurement.
The module-level extractor can fetch publicsuffix.org on its first cold lookup, try a GitHub mirror, and cache the parsed list below the user's cache directory. TLDEXTRACT_CACHE or cache_dir changes that location. TLDEXTRACT_CACHE_TIMEOUT bounds network waiting. A read-only filesystem, missing home directory, or blocked egress can therefore turn an ordinary hostname split into a startup problem. suffix_list_urls=() disables HTTP and uses the bundled snapshot.
Build one TLDExtract instance and reuse it. Each instance prepares its own suffix matcher on first use, so constructing one inside a request handler repeats work. The cache does not expire by age. Run tldextract --update deliberately, vendor a list through a file URL, or pin the bundled snapshot if identical answers across replicas matter. fallback_to_snapshot=True can hide a download failure by returning results from the package's older copy.
tldextract finds suffix boundaries and does not certify input. Free text can be returned as a domain, unknown final labels yield an empty suffix, and case plus Unicode or ACE spelling are preserved. Validate the hostname with a URL or IDNA layer before using it in an allowlist. Decide whether the PSL PRIVATE section counts for your tenancy model. Migrate registered_domain calls to top_domain_under_public_suffix before version 6.
Patterns
Split a hostname at its public suffix extract-basic
import tldextract
print(tldextract.extract('http://forums.news.cnn.com/'))
# ExtractResult(subdomain='forums.news', domain='cnn', suffix='com', is_private=False)
print(tldextract.extract('http://forums.bbc.co.uk/'))
# ExtractResult(subdomain='forums', domain='bbc', suffix='co.uk', is_private=False)The shared module extractor accepts hostnames and full URLs. Scheme, port, path, and query do not become domain labels.
Return the registrable site name registrable-domain
ext = tldextract.extract('http://forums.bbc.co.uk/path')
print(ext.top_domain_under_public_suffix) # 'bbc.co.uk'
print(ext.fqdn) # 'forums.bbc.co.uk'top_domain_under_public_suffix is the 5.x replacement for deprecated registered_domain. It is empty for an address with no recognized suffix.
Use only the packaged suffix snapshot disable-network-fetch
import tldextract
extract = tldextract.TLDExtract(suffix_list_urls=())
result = extract('http://www.google.com')An empty URL tuple prevents the first call from downloading the PSL. Results then stay tied to the installed package snapshot.
Share one configured extractor reuse-one-extractor
EXTRACT = tldextract.TLDExtract(suffix_list_urls=())
def site_of(url: str) -> str:
return EXTRACT(url).top_domain_under_public_suffixCreate the instance at module scope so request handlers reuse the prepared suffix matcher and the same cache policy.
Choose a writable cache and timeout custom-cache-location
import tldextract
extract = tldextract.TLDExtract(cache_dir='/var/cache/tldextract')
# Environment equivalents:
# TLDEXTRACT_CACHE=/var/cache/tldextract
# TLDEXTRACT_CACHE_TIMEOUT=2.0The default user cache may fail in a read-only container. TLDEXTRACT_CACHE_TIMEOUT limits the initial remote-list request.
Treat hosted tenants as separate sites private-domains
import tldextract
extract = tldextract.TLDExtract(include_psl_private_domains=True)
print(extract('waiterrant.blogspot.com'))
# ExtractResult(subdomain='', domain='waiterrant', suffix='blogspot.com', is_private=True)Private suffix handling is off by default. Enable it when each blogspot.com or github.io tenant must have its own boundary.
Refresh suffix data on a schedule update-suffix-list
# shell
tldextract --update
# Python
extract = tldextract.TLDExtract()
extract.update(fetch_now=True)The cache has no automatic age expiry. Schedule updates or replace the cached file if current PSL rules matter to the service.
Extract from an existing SplitResult extract-from-parsed-url
from urllib.parse import urlsplit
import tldextract
extract = tldextract.TLDExtract(suffix_list_urls=())
parts = urlsplit('https://a.b.example.com/path?q=1')
print(extract.extract_urllib(parts))extract_urllib is an instance method in 5.3.2. urlsplit handles URL structure first; tldextract then finds the suffix boundary.
Separate IP and local-host results handle-non-domains
print(tldextract.extract('http://127.0.0.1/x').ipv4)
print(tldextract.extract('https://[2001:db8::1]/').ipv6)
local = tldextract.extract('http://localhost:8080')
if not local.suffix:
print('No recognized public suffix')An empty suffix covers localhost, unknown endings, and invalid text. Check IP helpers and validate input rather than trusting domain alone.
Canonicalize before comparing sites normalize-case
res = tldextract.extract('HTTP://WWW.EXAMPLE.COM')
site = res.top_domain_under_public_suffix.lower()
print(site) # 'example.com'Suffix matching ignores case, but returned strings preserve it. IDNA inputs also need one validated A-label representation for security comparisons.
Load internal and vendored suffix rules custom-suffix-list
import tldextract
corp = tldextract.TLDExtract(extra_suffixes=['internal', 'corp.example'])
vendored = tldextract.TLDExtract(
suffix_list_urls=['file:///opt/psl/public_suffix_list.dat'],
cache_dir='/var/cache/tldextract',
fallback_to_snapshot=False,
)extra_suffixes covers names that will never enter the public list. Disabling fallback makes an unreadable vendored file fail instead of using the packaged snapshot.
Create a reverse-domain identifier reverse-domain-name
result = tldextract.extract('api.eu.example.co.uk')
print(result.reverse_domain_name)
# uk.co.example.eu.apiVersion 5.3.2 fixes extra dots in reverse_domain_name when either domain or suffix is empty. It does not normalize case or IDNA form.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| publicsuffix2 | PyPI | Choose it for an older PSL interface that works from a packaged list and follows the publicsuffix API. |
| publicsuffixlist | PyPI | Choose it when you want a compact PSL lookup package whose suffix data updates through package releases. |
| uritools | PyPI | Choose it when the real job is parsing, classifying, resolving, or composing URI components rather than finding registration boundaries. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

