breakitdown

Regex for a URL slug

Answer

^[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.

flags: g all matches · i ignore case · m ^ $ per line · s . matches newlines · u Unicode

JavaScript ✓PCRE ✓

No catastrophic-backtracking patterns found. (A heuristic check: nested and adjacent repeats, overlapping alternatives.)

  1. ^Start of the stringanchor
  2. [a-z0-9]One character: a-z (lowercase letters) or 0-9 (digits)character class
  3. +Repeat the class one or more times, as many as possible (greedy)quantifier
  4. (?:…)Non-capturing group: groups without rememberinggroup
  5. -The character '-' literallyliteral
  6. [a-z0-9]One character: a-z (lowercase letters) or 0-9 (digits)character class
  7. +Repeat the class one or more times, as many as possible (greedy)quantifier
  8. *Then repeat the whole group zero or more times, as many as possible (greedy)quantifier
  9. $End of the stringanchor

Examples

Checked at build time in JavaScript and in Python's re.

InputMatches?
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

Gotchas

Related explainers

← Explain any regular expression