Check whether your own links match this URL pattern

Paste the links you actually have — from a list, a log file, a paragraph of pasted text — into the tester below and see instantly which ones match and which don’t. The sample already loaded mixes three links that match with three that fail for three different reasons. The copy-paste pattern, explained token by token, and what it deliberately does not validate, follow underneath.

/ / gim
Flags

    Your pattern and test text never leave this tab. Matching runs in a same-origin Web Worker with a timeout, so a URL from an internal tool or a private report is never sent anywhere to be tested. That protection matters more than it looks: a single regular expression once caused a real, global outage — the timeout above is what keeps this page safe from the same failure mode.

    ^https?:\/\/[^\s/$.?#].[^\s]*$

    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

    TokenMeaning
    ^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 the i flag.

    What it deliberately does not match, and why

    InputVerdictWhy
    ftp://example.comRejectedOnly 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.comRejectedNo scheme at all. Browsers guess a scheme for bare domains; this pattern does not guess — it validates what is actually written
    http://RejectedEmpty host — the negated character class after :// requires at least one non-slash character
    www.example.com/pageRejectedSame 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 spacesPartially matchedMatching 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, but mailto: and tel: URLs have no // and no host at all — mailto:ada@example.com would 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 i flag 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 the i flag 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 // (like mailto:) 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