Relative URL Regex
Validates a root-relative URL path such as /path/to/page, including an optional query string and fragment, while rejecting protocol-relative (//host) and absolute URLs.
Regex Pattern
^\/(?!\/)[\w\-\/.]*(\?[^\s#]*)?(#[^\s]*)?$Pattern Breakdown
Hover over a token to see what it does.
| Token | Meaning |
|---|---|
| ^ | Anchors the match to the start of the string |
| \/ | Requires the string to start with a single leading slash |
| (?!\/) | Negative lookahead rejecting a second slash right after the first, which would make it a protocol-relative URL |
| [\w\-\/.]* | Zero or more path characters: letters, digits, underscore, hyphen, slash, and dot |
| (\?[^\s#]*)? | Optional query string starting with ? and not containing whitespace or # |
| (#[^\s]*)? | Optional fragment starting with # and continuing to the end |
| $ | Anchors the match to the end of the string |
Detailed Explanation
What it does
This pattern validates root-relative URL paths as used in routing, links, and redirects: strings that start with exactly one slash and are followed by path segments, an optional query string, and an optional fragment.
Why it works
The leading \/ requires a slash, and the negative lookahead (?!\/) immediately rules out a second slash, which is what distinguishes a plain relative path from a protocol-relative URL like //example.com. The path body is a simple character class covering common URL-safe characters, and the query and fragment are separated into their own optional groups so each can be present independently, with the whole match anchored so no scheme or trailing garbage sneaks in.
Common use cases
- Validating redirect targets on a server to prevent open-redirect attacks to external hosts
- Checking internal navigation links in a single-page application router
- Sanitizing a 'next' or 'return_to' query parameter before using it for a redirect
- Filtering a sitemap or crawl list down to same-site relative paths
Edge cases
- A bare root path of just '/' is accepted
- A path with both a query string and fragment, like /search?q=1#results, is accepted
- Protocol-relative URLs like //evil.com are intentionally rejected by the negative lookahead
- Absolute URLs with a scheme like https://example.com are rejected because they do not start with a slash
- Paths containing a space are rejected since the character class does not include whitespace
Limitations
- Does not percent-decode or validate percent-encoded sequences in the path
- Does not allow characters like parentheses or commas that are technically valid in URL paths
- Does not validate the structure or key/value pairs inside the query string beyond excluding whitespace and #
- Cannot distinguish a semantically valid application route from an arbitrary matching string
Interactive Tester
Edit the pattern or text below — matching runs live in your browser.
Test Cases
Editable — add your own inputs to see if they pass.
| Input | Expected | Result | |
|---|---|---|---|
| Pass | |||
| Pass | |||
| Pass | |||
| Pass | |||
| Pass | |||
| Pass | |||
| Pass | |||
| Pass | |||
| Pass |
Language Variants
Production-ready examples in 12 languages.
const relativeUrlRegex = /^\/(?!\/)[\w\-\/.]*(\?[^\s#]*)?(#[^\s]*)?$/;
console.log(relativeUrlRegex.test('/path/to/page?q=1#frag')); // trueCommon Mistakes
Omitting the negative lookahead and accidentally accepting //evil.com as a 'relative' URL, opening an open-redirect vulnerability
Fix: Always exclude a leading double slash explicitly, since browsers treat // as protocol-relative to the current scheme
Using this pattern to validate any string containing a path, including ones with a scheme like https://example.com/page
Fix: Anchor the pattern and confirm the very first character is a single slash, not part of a longer URL
Forgetting Go's RE2 engine does not support lookahead, then trying to reuse the same pattern unmodified
Fix: In RE2-based engines, check for a leading // with a separate string comparison instead of a lookahead assertion
Performance Notes
- The negative lookahead only inspects a single character, so it adds negligible overhead
- Anchoring with ^ and $ avoids scanning for a match at multiple starting positions
- The path character class is a simple bounded set with no nested quantifiers, so there is no catastrophic backtracking risk
Browser Compatibility
| Engine | Supported | Notes |
|---|---|---|
| Chrome | Yes | — |
| Firefox | Yes | — |
| Safari | Yes | — |
| Edge | Yes | — |
| Node.js | Yes | Lookahead assertions are fully supported in the V8 regex engine |