πŸ“
Blog articleA deep-dive guide from the Domain Extractor Online team. Links back to the tool at the bottom.

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.uk is a valid hostname. The registrable part β€” what you would actually buy from a registrar β€” is example.co.uk. Everything to the left is a subdomain.
  • Public suffixes. co.uk looks 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 for github.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 typeExampleExtraction strategy
URLhttps://blog.example.com/post?id=5Parse with URL(), take hostname
Email address[email protected]Split on @, take second half
Bare hostname in textVisit api.stripe.com for detailsRegex 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 leading www.; 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:

  1. Find the longest matching public suffix from the end of the hostname.
  2. The registrable domain is that suffix plus one more label to the left.

Some examples:

HostnamePublic suffixRegistrable domain
www.example.comcomexample.com
a.b.example.co.ukco.ukexample.co.uk
user.github.iogithub.iouser.github.io
app.example.appspot.comappspot.comexample.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 HTML href="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.de or δΎ‹γˆ.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.COM and example.com are 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