Regex for a URL slug
^[a-z0-9]+(?:-[a-z0-9]+)*$ matches lowercase letters and digits in runs joined by single hyphens. It rejects leading, trailing and doubled hyphens, uppercase, spaces and accents.
JavaScript ✓PCRE ✓
No catastrophic-backtracking patterns found. (A heuristic check: nested and adjacent repeats, overlapping alternatives.)
^Start of the stringanchor[a-z0-9]One character: a-z (lowercase letters) or 0-9 (digits)character class+Repeat the class one or more times, as many as possible (greedy)quantifier(?:…)Non-capturing group: groups without rememberinggroup-The character '-' literallyliteral[a-z0-9]One character: a-z (lowercase letters) or 0-9 (digits)character class+Repeat the class one or more times, as many as possible (greedy)quantifier*Then repeat the whole group zero or more times, as many as possible (greedy)quantifier$End of the stringanchor
Examples
Checked at build time in JavaScript and in Python's re.
| Input | Matches? |
|---|---|
my-first-post | ✓ match |
post-2024 | ✓ match |
a | ✓ match |
My-First-Post | ✗ no match |
my--post | ✗ no match |
-post | ✗ no match |
post- | ✗ no match |
my_post | ✗ no match |
café-menu | ✗ no match |
hello world | ✗ no match |
Try your own text
One string per line: each line is tested on its own (the m flag is added here).
How it works
The structure is one word, then zero or more groups of hyphen plus word. Every hyphen therefore has a word on both sides. That is how the pattern rules out -post, post- and my--post without lookaheads.
It is also safe from slow matching. Each repetition of the group must start with -, which [a-z0-9] can never match, so the engine has only one way to split any input.
Variants
/[^a-z0-9]+/gTo create a slug: lowercase the text, replace every match with-, then trim hyphens from the ends./^[a-z0-9]+(?:[-_][a-z0-9]+)*$/Also allows underscores as separators.
Gotchas
- Underscores fail. Google's URL guidance recommends hyphens over underscores as word separators.
- Accented letters fail. To build a slug from a title, first run
s.normalize("NFD").replace(/[\u0300-\u036f]/g, "")to drop combining accents, then lowercase. - A slug that matches can still collide with another page. Uniqueness is a database check.
Related explainers
- Regex for an http or https URLA regex that checks a string is an http or https URL, where it falls short, and why new URL() is the...
- Regex for a UUIDRegexes for UUIDs in the standard 8-4-4-4-12 form: any version, version 4 only, and versions 1-8 with the...
- Regex to trim leading and trailing whitespaceHow ^\s+|\s+$ with the g flag trims a string, why String.prototype.trim() is better, and the 2016 Stack...