Match a domain name
Pick domain names like sub.example.co.uk out of free text, allowing the hyphens real hostnames use while still requiring at least one dot.
Last updated
\b[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+\b
Matches
- Visit example.com for details
- sub-domain.example.co.uk works too
- reach us at name@example-server.io please
Doesn’t match
- localhost has no dot at all
- just_underscores_here nothing
Why it’s written this way
Each label is [a-z0-9]([a-z0-9-]*[a-z0-9])? — it must start and end with a letter or digit, with hyphens allowed only in the middle, which is exactly how DNS labels are defined (no leading or trailing hyphen). The label group then repeats after a literal dot one or more times via (\.[a-z0-9]...)+, so multi-level names like a.b.co.uk match in one pass.
The i flag makes the letter ranges case-insensitive without needing A-Z alongside a-z everywhere, since real-world hostnames show up in both cases in logs and copy-pasted text.
Edge cases to know
- →There is no TLD list, so made-up endings like .zzz match just as happily as .com — this checks shape, not registration.
- →Because it is unanchored, it matches the domain portion inside an email address too — reach@example.com will highlight example.com, which is usually what you want but is worth knowing.
- →It does not handle internationalized domain names (IDN) written as raw Unicode — only ASCII/punycode labels.
Part by part
- [a-z0-9]
- A label must start with a letter or digit — never a hyphen. This is a real DNS rule, not a stylistic choice.
- ([a-z0-9-]*[a-z0-9])?
- The middle of a label, optionally, ending again on a letter or digit. The structure is what forbids a trailing hyphen while allowing internal ones.
- (\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+
- One or more further dot-separated labels. The + is what requires at least one dot, so a bare hostname does not match.
Common mistakes
[a-z0-9.-]+A flat character class accepts leading hyphens, trailing hyphens, doubled dots and a string of dots on its own. Every rule that makes a domain a domain lives in the structure, not in the alphabet.
\b[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+\b\b[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*\bA star instead of a plus makes the dotted part optional, so any single word in the text becomes a domain. It is a one-character change that turns a specific pattern into one that matches prose.
\b[a-z0-9-]+(\.[a-z0-9-]+)+\bSimplifying each label to one class readmits the leading and trailing hyphen that the nested form exists to forbid — so -bad-.example.com matches.
Questions
Does it enforce the 63-character limit on a label?
No, nor the 253-character total. Both are real DNS limits and both are easier to check after matching than to encode as a quantifier, especially since exceeding them is rare in practice.
Will it match a domain inside an email address or URL?
Yes — it has no idea what surrounds it. Scanning "mail bob@example.com now" finds example.com. Match the larger structure first when you need the domain in context rather than every domain-shaped substring.
Does it accept internationalised domains?
Only as punycode, such as xn--80ak6aa92e.com, because the classes are ASCII. Natively written unicode domains will not match, so normalise before testing if you expect them.
Does a match mean the domain is registered?
No. It confirms the name is well-formed, nothing more. notarealdomain.example matches perfectly and resolves nowhere — only a DNS lookup answers the question you are actually asking.