How Domain Extraction Logic Works β From Text, URL, and Email
Published August 27, 2026 Β· 12 min read Β· By Domain Extractor Team
If you've ever tried to write a regex to "just grab all the domains from this text", you've probably discovered that the problem is deeper than it looks. Emails, subdomains, ports, multi-part TLDs, IP addresses, URLs without a scheme, and the ever-growing Public Suffix List all conspire to make naive extraction unreliable. This post walks through the actual logic behind a production-grade domain extractor: what to match, what to reject, and why.
Table of contents
1. What exactly is a "domain"?
A domain name is a human-readable identifier for a resource on the Internet, composed of one or more labels separated by dots. Each label is 1-63 characters of ASCII letters, digits, or hyphens (not starting or ending with a hyphen), and the whole thing ends in a Top-Level Domain (TLD) like com, org, or id.
But that clean definition hides two important nuances:
- Hostname vs. registrable domain.
api.v2.blog.example.co.ukis a valid hostname. The registrable part β what you would actually buy from a registrar β isexample.co.uk. Everything to the left is a subdomain. - Public suffixes.
co.uklooks like a normal two-label domain, but you can't register it directly β it's a public suffix maintained by Nominet. The same is true forgithub.io,appspot.com, and thousands of others. Handling this correctly requires the Public Suffix List.
These two ideas β hostname vs. registrable, and public suffixes β are the reason "just use a regex" isn't enough for reliable extraction.
2. Three inputs, three strategies
Real-world text usually has domains hidden inside three shapes:
| Input type | Example | Extraction strategy |
|---|---|---|
| URL | https://blog.example.com/post?id=5 | Parse with URL(), take hostname |
| Email address | [email protected] | Split on @, take second half |
| Bare hostname in text | Visit api.stripe.com for details | Regex match, then validate |
The reason a good extractor gets high accuracy is that it applies all three strategies to a single input and merges the results, then deduplicates. Regex alone misses URLs where the scheme confuses the pattern; URL-parsing alone misses bare hostnames.
3. The extraction regex, explained
Here's a compact but effective pattern for finding hostname-shaped substrings:
/(?:(?:https?|ftp):\/\/)?(?:www\.)?([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(?::[0-9]{1,5})?(?:\/[^\s]*)?/gi
Let's break it down:
(?:(?:https?|ftp):\/\/)?β optionally consume a scheme; we don't need a scheme to match a bare hostname.(?:www\.)?β optionally consume a leadingwww.; the label itself will still be captured.([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+β one or more labels ending in a dot. The inner character class enforces the rule that a label starts and ends with an alphanumeric and has 0β61 middle chars.[a-zA-Z]{2,}β the final TLD label, at least two letters.(?::[0-9]{1,5})?β optional port.(?:\/[^\s]*)?β optional path.
What this regex gets right: it correctly finds hostnames with or without schemes, and rejects things like 3.14 or file.txt because the trailing "label" must be at least two letters and there must be at least one prior label followed by a dot.
What it doesn't do: it doesn't know whether the final label is a real TLD. It'll match foo.bar even though .bar isn't a registered TLD. That's step 4.
4. Validation with the IANA TLD list
IANA publishes an authoritative list of all delegated TLDs β as of 2026, around 1,500 entries covering everything from the classics (.com, .org) to country codes (.id, .uk, .vn) to new gTLDs (.dev, .app, .blog).
After the regex matches, you extract the last label and check membership in this list:
function isValidTLD(candidate) {
const parts = candidate.toLowerCase().split('.');
const tld = parts[parts.length - 1];
return IANA_TLDS.has(tld);
}
This one check removes the vast majority of false positives β random dotted strings like version.2, section.a, note.txt all get rejected.
5. Reducing to registrable domain (Public Suffix List)
Once you have a valid hostname like a.b.example.co.uk, the question becomes: what part of this can someone actually own? The answer isn't "the last two labels" β that would give you co.uk, which is a public suffix. The correct answer is example.co.uk.
The Public Suffix List encodes these rules. The algorithm is:
- Find the longest matching public suffix from the end of the hostname.
- The registrable domain is that suffix plus one more label to the left.
Some examples:
| Hostname | Public suffix | Registrable domain |
|---|---|---|
www.example.com | com | example.com |
a.b.example.co.uk | co.uk | example.co.uk |
user.github.io | github.io | user.github.io |
app.example.appspot.com | appspot.com | example.appspot.com |
The third and fourth rows are the tricky ones β GitHub Pages and Google App Engine are on the PSL as public suffixes, so anything below them is a "registrable" domain in the practical sense.
6. Edge cases that will bite you
Once your extractor works on happy-path text, real-world inputs will surface these problems:
- Punctuation glued to hostnames:
Visit example.com,β the comma sticks to the match. Strip trailing punctuation (. , ; : ! ?) after the regex. - Bracketed URLs: markdown-style
[link](https://example.com)or HTMLhref="https://example.com". Regex captures the domain; the outer chars are your job to strip. - URL shorteners:
bit.ly/abc123,t.co/xyzβ these are valid hostnames but not the target domain. You'd need a resolver (out of scope for extraction). - IDN (internationalised domains):
mΓΌller.deorδΎγ.jp. If you allow Unicode in your regex character class, you can match these; otherwise convert to Punycode (xn--mller-kva.de) first. - IPv4/IPv6 hostnames:
http://192.168.1.1. Different problem; usually you don't want these in a "domain" list. - Email display names:
"Alice" <[email protected]>. Match the email, then extract the domain part. - File extensions that look like TLDs:
report.doc,note.jp,data.io. These do have real TLDs on the right, so the TLD-list check alone won't save you. Adding context checks (is there a scheme? is the left side a valid label with at least one dot?) helps. - Case-insensitivity: Dedupe by lowercased hostname.
Example.COMandexample.comare the same domain.
7. Complete algorithm (JavaScript)
Putting it all together, here's the pipeline the tool at domainextractoronline.com uses:
function extractDomains(text, options = {}) {
const {
registrableOnly = false,
removeDuplicates = true,
tldFilter = null, // array of allowed TLDs, or null for all
} = options;
// 1. Regex-match all hostname-shaped strings
const hostnameRegex = /(?:https?:\/\/)?(?:www\.)?([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}/gi;
const matches = [...text.matchAll(hostnameRegex)].map(m => m[0]);
// 2. Extract email domains
const emailRegex = /[a-zA-Z0-9._-]+@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi;
const emailDomains = [...text.matchAll(emailRegex)].map(m => m[1]);
// 3. Clean each candidate
let domains = [...matches, ...emailDomains].map(cleanHostname);
// 4. Validate TLD
domains = domains.filter(d => hasValidTLD(d));
// 5. Optionally reduce to registrable domain
if (registrableOnly) {
domains = domains.map(getRegistrableDomain);
}
// 6. Optional TLD whitelist
if (tldFilter) {
domains = domains.filter(d => tldFilter.includes(d.split('.').pop()));
}
// 7. Deduplicate case-insensitively
if (removeDuplicates) {
domains = [...new Set(domains.map(d => d.toLowerCase()))];
}
return domains.sort();
}
function cleanHostname(raw) {
return raw
.replace(/^https?:\/\//i, '')
.replace(/^www\./i, '')
.replace(/[\/:?#].*$/, '') // drop path/port/query/fragment
.replace(/[.,;!?]+$/, '') // trailing punctuation
.toLowerCase();
}
The two helpers hasValidTLD and getRegistrableDomain are where you plug in the IANA list and Public Suffix List, respectively. Both are small enough (~50KB minified) to ship in the browser.
8. Try it β no code needed
If you don't want to build all this yourself, our free tool implements every step above:
Everything runs in your browser β pasted text never leaves your device. And because the JavaScript engine is small and MIT-friendly, you can inspect /js/domain-extractor.js if you want to see the full production implementation.
Further reading
- Root Domain vs Subdomain: The Complete Guide β a companion post going deep on the PSL and when it matters for SEO.
- How to Build a Chrome Extension for Domain Extraction β turn the algorithm above into a browser extension.
- The WHATWG URL Standard β the definitive spec for URL parsing.
- The Public Suffix List β Mozilla's community-maintained list of public suffixes.