What this pattern is for
This is a recogniser, not a parser: it answers “does this string
look like an http(s) URL” — useful for auto-linking plain text, for a quick form
check, or for filtering log lines. It uses the i flag so
HTTPS://Example.COM matches as readily as the lowercase form. It deliberately
does not try to validate the path, query string or fragment in any structural way —
see “what it deliberately does not do” below for why that is the right call, not
a shortcut.
Token by token
| Token | Meaning |
|---|---|
^ | Start of the string |
https? | Literal http, with an optional trailing s — matches both schemes with one alternative instead of (http|https) |
:\/\/ | A literal ://. The slashes are escaped as \/, which is optional in JavaScript (only / itself needs escaping when it would otherwise end the pattern, e.g. inside a /…/ literal) but kept here for clarity and portability to engines that require it |
[^\s/$.?#] | The first character of the host: anything except whitespace or the characters / $ . ? # — this stops the pattern accepting http:// with nothing after it, or http:///path with an empty host |
. | Any one more character — together with the class above, guarantees the host is at least two characters long |
[^\s]* | Everything else — host, port, path, query, fragment — as long as none of it is whitespace |
$ | End of the string |
What it matches
https://example.com— the plain case.http://sub.example.co.uk/path?q=1#section— subdomain, path, query string and fragment, none of them inspected structurally.HTTPS://Example.COM/— case-insensitive scheme and host, because of theiflag.
What it deliberately does not match, and why
| Input | Verdict | Why |
|---|---|---|
ftp://example.com | Rejected | Only http/https are in the pattern’s scheme alternative on purpose — broaden it with (https?|ftp|mailto) if you genuinely need more schemes, but note mailto: URLs have no // and no host, so this shape does not fit them |
example.com | Rejected | No scheme at all. Browsers guess a scheme for bare domains; this pattern does not guess — it validates what is actually written |
http:// | Rejected | Empty host — the negated character class after :// requires at least one non-slash character |
www.example.com/page | Rejected | Same as above: no scheme, so not recognised as a URL by this pattern even though a browser address bar would happily accept it |
http://example.com/a path with spaces | Partially matched | Matching stops at the first space, because [^\s]* excludes whitespace — an unencoded space in a path is itself invalid in a URL, so this is arguably correct behaviour, not a bug |
Why this does not validate URL structure, and what does
The pattern intentionally treats everything after the host as “any non-whitespace
text”. It does not check that the query string is well-formed
key=value&key2=value2 pairs, that percent-encoding is correct, or that the
host is a real, resolvable domain rather than a syntactically plausible one. Trying to
encode full URL structure — RFC 3986’s grammar for scheme, authority, userinfo,
host, port, path, query and fragment, each with their own allowed character sets and
percent-encoding rules — into one regex produces something enormous and fragile.
JavaScript already ships a real URL parser for this: new URL(str) throws on
anything invalid and hands back .hostname, .pathname,
.searchParams and the rest, already decoded and validated by a specification-
compliant parser rather than a hand-rolled character class. Reach for the regex to spot a
link in free text; reach for URL the moment you need to inspect or manipulate
one.
Internationalised domain names are the sharpest edge of this trade-off. A host like
müller.example or its Punycode form xn--mller-kva.example is a
legal, resolvable hostname, but reasoning about which raw Unicode code points are valid in a
domain label is exactly the kind of question UTS #18
exists to answer precisely and a hand-written character class does not attempt to. This
pattern accepts such a host anyway, because [^\s]* does not look closely enough
to reject it — that permissiveness is deliberate here, but it means the pattern is not
doing IDN validation, only failing to get in its way.
Flavour: this is JavaScript’s regex
The pattern targets JavaScript’s built-in
RegExp,
per the grammar in ECMA-262.
It uses only literals, one negated character class, ? and *, so it
ports to PCRE or Python’s re unchanged. See the
cookbook’s flavour-honesty section for the constructs (named
groups, lookbehind, possessive quantifiers) that do not.
Worked examples
1. Auto-linking plain text
A common use: scan a block of user-submitted text and wrap anything shaped like a URL
in an anchor tag. With the g flag on, String.prototype.replace
and this pattern can do that in one call — but building the anchor tag from matched
text must use textContent/DOM APIs or a properly escaping template, never
string concatenation into raw HTML, or a URL like
https://x.com/"><script>alert(1)</script> becomes a stored
cross-site-scripting bug the moment someone else’s comment gets rendered.
2. Filtering log lines that contain a link
With g on and no anchors, the same pattern (drop the ^/$) finds every URL inside a much longer line — useful for pulling
outbound links out of a referrer log or a chat export before feeding them to something
stricter, like the URL constructor, for structural validation one at a
time.
3. A slug-friendly link, and the tool for making one
Turning a page title into the path segment of a URL — My Great Post!
into my-great-post — is a different regex problem entirely: it is about
producing a valid slug, not recognising a whole URL. See the
dedicated slug page for that pattern and its own worked examples.
4. Deciding whether a string is a URL before fetching it
Some tools accept either a raw block of text or a link to fetch and process (paste an article, or paste a link to one). Testing the input against this pattern first — before attempting a network request — lets the tool branch cleanly: treat it as a URL and fetch, or treat it as literal text and process directly. The anchors matter here specifically, because without them a block of prose that merely mentions a URL partway through would be misclassified as one.
Using this pattern in your own code
The pattern uses nothing outside the common subset every mainstream regex engine shares, so the same characters work unchanged across a browser, a server runtime, and the shell.
// JavaScript
const URL_RE = /^https?:\/\/[^\s/$.?#].[^\s]*$/i;
URL_RE.test('https://example.com'); // true
URL_RE.test('ftp://example.com'); // false
# Python's re module — add re.IGNORECASE explicitly rather than an inline flag,
# since Python spells that (?i) the same way JavaScript does but the case-
# insensitive constructor argument is the more common idiom in Python code
import re
URL_RE = re.compile(r'^https?://[^\s/$.?#].[^\s]*$', re.IGNORECASE)
bool(URL_RE.match('HTTPS://Example.COM/')) # True
# grep -Pi, case-insensitive PCRE mode, scanning a file of candidate lines
grep -Pi '^https?://[^\s/$.?#].[^\s]*$' lines.txt
Note the two escaped slashes in the JavaScript literal (\/\/) simply become
plain slashes in the Python and grep versions above — JavaScript only requires
escaping a / when it appears inside a /…/ literal, where an
unescaped one would end the pattern early; Python and grep have no such literal delimiter
convention for this pattern, so the backslashes are dropped, not because the character means
anything different.
Common mistakes when adapting this pattern
- Adding more schemes without checking their shape. Changing
https?to(https?|mailto|tel)looks like a small extension, butmailto:andtel:URLs have no//and no host at all —mailto:ada@example.comwould fail the very next token,:\/\/, which those schemes never contain. A pattern covering multiple schemes with genuinely different structures needs a separate branch per structure, not one shared tail. - Dropping the
iflag and manually lowercasing the scheme instead.str.toLowerCase().match(URL_RE)without the flag will also lowercase the path and query string, which can silently break a case-sensitive path segment or query parameter. Keep theiflag scoped to matching, not to a preprocessing step that touches the whole string. - Assuming a match means a safe redirect target. This pattern says nothing about whether a URL points somewhere your application should trust — an open-redirect vulnerability is exactly a case where the string is a perfectly well-formed URL that still should not be followed. That is an allowlist-of-hosts problem, decided in application logic, not something a shape check can rule on.
Frequently asked questions
Why does the pattern accept an unresolvable domain?
Because DNS resolution needs a network request, and this pattern — like all client-side regex — only ever inspects the text in front of it. A syntactically valid host and a domain that actually resolves are different questions; confirming the second means making a request your JavaScript would have to send somewhere.
Should I use this instead of the URL constructor?
Only for spotting a URL inside a larger string. The moment you
need to know the host, the path, or whether the query string parses, use
new URL(str). It is a real parser, throws a clear error on genuinely invalid
input, and handles encoding correctly — a regex reimplementing that is strictly worse
for the same job.
Can I make it require www.?
Yes, but think twice — www. is a convention, not
a requirement, and plenty of legitimate sites (including this one) do not use it. If you
need it: insert (www\.)? as optional, or make it mandatory only if your
specific use case genuinely requires that subdomain.
Why does a trailing punctuation mark get swallowed into the match?
Because a period, comma or closing parenthesis at the end of a
sentence is not whitespace, so [^\s]* happily includes it: “Visit
https://example.com.” matches through the trailing full stop. Auto-linkers commonly
trim a small set of trailing punctuation characters off the end of a match for this exact
reason — it is a known, common post-processing step, not a flaw unique to this
pattern.
Does it handle a port number, like :8080?
Yes — http://localhost:8080/api matches, because
everything after the host is swallowed unstructured by [^\s]*. It is matched,
not validated: http://localhost:999999, an invalid port number, also
matches. Use new URL() if the port number itself needs to be range-checked.
Why doesn’t it match a relative path like /about?
By design — this pattern recognises absolute
http(s) URLs specifically. A relative path has no scheme and no host, so
matching it needs a different, much looser pattern with correspondingly less
confidence that the string is actually a link.
Does it handle a URL with a username and password, like user:pass@host?
Yes, but only because it is unstructured about it, not because it
understands that syntax specifically —
https://user:pass@example.com/ matches, since everything after the host
character class is swallowed by [^\s]* without being parsed into userinfo,
host and path separately. If you need to actually extract the username and password,
new URL(str).username and .password do that correctly; this
pattern only confirms the whole thing is URL-shaped.
Is my URL sent anywhere when I test it here?
No. Matching runs inside a same-origin Web Worker — a background thread of this same page — and there is no upload endpoint anywhere on this site’s regex tool. Open the Network panel in developer tools and watch it stay empty while you type.
Limitations
- Recognises the shape of a URL; does not parse it. Use
new URL()for structural access. - Does not check DNS resolution, HTTP reachability, or certificate validity — all of those require a network request this client-side tool does not make.
- Only matches
http/https; extend the scheme alternation for others, keeping in mind schemes without//(likemailto:) do not fit this pattern’s shape at all. - Swallows trailing sentence punctuation into the match; strip it in a post-processing step if you are auto-linking prose.
Related
- Regex for email validationAn anchored pattern with the same honesty about its limits
- Regex for a URL slugThe path-segment half of a URL, as its own smaller problem
- Regex for an IPv4 addressFor when the host in a URL is a bare IP rather than a domain
- About this toolWhy it exists and how the tester is built
- JSON formatterAnother text tool that runs entirely in your browser
- All toolsSee everything on this site