tld.js
Parses hostnames and URLs to reliably extract domains, subdomains, and public suffixes using Mozilla's public suffix list.
Repository Health
Technical Analysis
tld.js is a small, dependency-light JavaScript library for answering questions about hostnames and URLs that plain string-splitting gets wrong: what is the registrable domain of mail.google.com? What is the subdomain of a.b.ide.kyoto.jp? Is big.data’s TLD actually a well-known one? Instead of guessing from the last dot, it looks up each hostname against Mozilla’s public suffix list, which correctly treats multi-part suffixes like co.uk or s3.amazonaws.com as a single unit.
The library ships as a factory function that builds an instance around a Trie of suffix rules, and exposes both a single parse() call that returns every derived property at once and single-purpose shorthands (getDomain, getSubdomain, getPublicSuffix, tldExists, isValidHostname) for callers who only need one value. It runs equally well in Node.js and in the browser (via browserify/webpack), and supports custom valid-host lists for cases like localhost that aren’t real TLDs but should still resolve to a domain.
What You Get
- A single
parse(url)call that returns hostname, isValid, isIp, tldExists, publicSuffix, domain, and subdomain in one pass - Single-purpose shorthand functions (
getDomain,getSubdomain,getPublicSuffix,tldExists,isValidHostname) for callers who only need one value - A bundled, Trie-indexed copy of Mozilla’s public suffix list with an opt-in
--tldjs-update-rulesinstall flag to refresh it - A
fromUserSettings()factory for creating a customized instance with additional valid hosts (e.g.localhost) or a custom hostname-extraction function - Browser-ready builds (
tld.js,tld.min.js) alongside the CommonJS build, so the same API works client- and server-side
Common Use Cases
- Grouping or comparing URLs by their registrable domain (e.g. treating
mail.google.comanddocs.google.comas the same site) - Validating and normalizing user-submitted hostnames before storing or looking them up
- Cookie-scoping and same-site logic that needs the correct eTLD+1 rather than a naive last-two-labels guess
- Filtering or classifying links by public suffix in crawlers, ad-blockers, or analytics pipelines
- Detecting whether a given string is an IP address versus a real hostname before applying domain logic
Under The Hood
Architecture
tld.js is organized as a single factory() function in index.js that assembles a set of independent, single-responsibility modules under lib/ — clean-host.js (hostname extraction from arbitrary URL forms), is-valid.js (RFC 1035 validation), is-ip.js (IP detection), suffix-trie.js (a Trie built from the bundled rules.json public suffix list), public-suffix.js, domain.js, and subdomain.js. The core parse() function threads a hostname through these stages with numeric step flags (TLD_EXISTS, PUBLIC_SUFFIX, DOMAIN, SUB_DOMAIN, ALL) that let single-purpose shorthands like getDomain() stop early instead of computing the full result — a deliberate laziness optimization given the library’s own published benchmarks. fromUserSettings() re-invokes the same factory with overridden rules, validHosts, or extractHostname, so customization happens by producing a new instance rather than mutating global state.
Tech Stack
The published library is plain CommonJS JavaScript (ES5-style var declarations, no TypeScript, no build step for the main entry point) with a single runtime dependency, punycode@^2.3.1. Browser bundles (tld.js, tld.min.js) are produced from the same source via browserify and uglify-js, and package.json’s exports map points import at the browserified build and require at index.js. Tooling includes ESLint 9’s flat config (eslint.config.mjs) with the js/recommended ruleset, mocha + nyc for tests and coverage, benchmark.js for the documented perf numbers, and a GitHub Actions workflow (.github/workflows/main.yml) that installs, tests, reports coverage via Coveralls, and publishes to npm on tagged pushes.
Code Quality
test/tld.js and test/publicsuffix.js provide extensive mocha/expect.js assertions covering edge cases (malformed hostnames, IP literals, trailing dots, case sensitivity, maximum-length hostnames), and nyc is configured with check-coverage: true plus HTML/lcov/text reporting, with posttest chaining into eslint. Functions carry JSDoc @param/@return/@typedef annotations throughout lib/ and index.js even though the project isn’t written in TypeScript, which gives editor tooling reasonable type hints. One gap: the README links to a CONTRIBUTING.md that isn’t actually present in the repository.
API Design
The public surface is deliberately small and consistently named — parse, tldExists, getDomain, getSubdomain, getPublicSuffix, isValidHostname, plus fromUserSettings for customization — and every method is documented in the README with runnable input/output examples. The deprecated isValid alias uses Node’s util.deprecate to warn callers toward isValidHostname instead of silently breaking them. Getting started requires nothing more than require('tldjs') and calling a function; the tradeoff is that no TypeScript type declarations are published with the package, so TypeScript consumers must rely on community @types or write their own.