A slug is the part of a URL that identifies a specific page in a human-readable way. In https://example.com/blog/what-is-a-slug, the slug is what-is-a-slug. It's short, descriptive, lowercase, and uses hyphens instead of spaces.
Slugs appear everywhere: blog posts, product pages, user profiles, categories. Understanding how to create good slugs — and how to generate them programmatically — is fundamental web development knowledge.
Why slugs matter
SEO
Search engines read slugs. A URL like /blog/what-is-a-slug tells Google exactly what the page is about before it even crawls the content. Compare:
| Bad URL | Good URL |
|---|---|
/post?id=4821 |
/blog/what-is-a-slug |
/p/2026/07/12/untitled |
/products/wireless-keyboard |
/article/article-title-here-1234 |
/guide/css-flexbox |
Keywords in slugs can improve click-through rates from search results, because users see the URL and get a preview of what the page contains.
Usability
Readable URLs are easier to share, remember, and type. A user who sees /products/red-leather-wallet immediately knows where they're going. A user who sees /p?ref=839&cat=22&id=5571 does not.
Stability
Slugs should be permanent. If you change a slug after publishing, old links and bookmarks break. Set slugs carefully upfront, and use 301 redirects if you ever need to change one.
Anatomy of a good slug
/blog/how-to-make-french-press-coffee
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the slug
Rules for a well-formed slug:
- Lowercase only —
/My-Postand/my-postcan be treated as different URLs by some servers. - Hyphens as separators — not underscores, not spaces, not dots. Google treats hyphens as word separators; underscores are treated as connectors (
my_post= one word in Google's eyes). - Only URL-safe characters — letters, digits, hyphens. No
&,?,#,%, accented characters (encode or transliterate them). - No leading or trailing hyphens —
-my-post-looks odd and can confuse parsers. - No consecutive hyphens —
my--postis usually a sign of a bug in the slugify function. - Reasonably short — 3–7 words is typical. Long slugs are harder to share and may get truncated.
The slugify algorithm
Converting arbitrary text into a clean slug follows these steps:
- Lowercase the string.
- Transliterate or remove accented/Unicode characters (
café→cafe). - Replace any character that isn't a letter, digit, or hyphen with a hyphen.
- Collapse multiple consecutive hyphens into one.
- Strip leading and trailing hyphens.
JavaScript
function slugify(text) {
return text
.toString()
.toLowerCase()
.normalize("NFD") // split accented chars into base + diacritic
.replace(/[\u0300-\u036f]/g, "") // remove diacritic marks
.trim()
.replace(/[^a-z0-9\s-]/g, "") // remove non-alphanumeric (except space/hyphen)
.replace(/[\s_-]+/g, "-") // replace spaces/underscores with hyphen
.replace(/^-+|-+$/g, ""); // strip leading/trailing hyphens
}
slugify("Hello, World!"); // "hello-world"
slugify("Café au lait"); // "cafe-au-lait"
slugify(" multiple spaces "); // "multiple-spaces"
slugify("100% Pure & Natural"); // "100-pure-natural"
Or use the battle-tested slugify npm package:
import slugify from "slugify";
slugify("Hello World!", { lower: true, strict: true });
// "hello-world"
Python
import re
import unicodedata
def slugify(text: str) -> str:
# Normalize Unicode (NFD decomposes accented chars)
text = unicodedata.normalize("NFD", text.lower())
# Remove diacritics (combining characters)
text = "".join(c for c in text if unicodedata.category(c) != "Mn")
# Replace non-alphanumeric with hyphen
text = re.sub(r"[^a-z0-9]+", "-", text)
# Strip leading/trailing hyphens
return text.strip("-")
print(slugify("Hello, World!")) # hello-world
print(slugify("Café au lait")) # cafe-au-lait
print(slugify("100% Pure & Natural")) # 100-pure-natural
The python-slugify library handles edge cases automatically:
from slugify import slugify
print(slugify("Hello, World!")) # hello-world
print(slugify("Ñoño")) # nono
Go
package main
import (
"regexp"
"strings"
"unicode"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
)
func slugify(s string) string {
// Lowercase
s = strings.ToLower(s)
// Transliterate accented characters
t := transform.Chain(norm.NFD, transform.RemoveFunc(func(r rune) bool {
return unicode.Is(unicode.Mn, r) // remove combining marks
}), norm.NFC)
s, _, _ = transform.String(t, s)
// Replace non-alphanumeric with hyphen
re := regexp.MustCompile(`[^a-z0-9]+`)
s = re.ReplaceAllString(s, "-")
// Trim hyphens
return strings.Trim(s, "-")
}
func main() {
println(slugify("Hello, World!")) // hello-world
println(slugify("Café au lait")) // cafe-au-lait
println(slugify("100% Pure & Natural")) // 100-pure-natural
}
PHP
function slugify(string $text): string {
// Transliterate accented characters
$text = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", $text);
// Lowercase
$text = strtolower($text);
// Replace non-alphanumeric with hyphen
$text = preg_replace("/[^a-z0-9]+/", "-", $text);
// Trim hyphens
return trim($text, "-");
}
echo slugify("Hello, World!"); // hello-world
echo slugify("Café au lait"); // cafe-au-lait
echo slugify("100% Pure & Natural"); // 100-pure-natural
Laravel provides a built-in helper:
use Illuminate\Support\Str;
Str::slug("Hello, World!"); // hello-world
Str::slug("Café au lait", "-"); // cafe-au-lait
Handling duplicates
When two pieces of content produce the same slug ("React Tips" and "React Tips!" both become react-tips), you need a uniqueness strategy. Common approaches:
Append a counter:
react-tips
react-tips-2
react-tips-3
Append an ID:
react-tips
react-tips-a3f9
Include a date:
react-tips-2026-07
Most CMS platforms and blog engines handle this automatically.
Slugs vs IDs in URLs
You'll often see hybrid URLs like /blog/4821-what-is-a-slug. This combines an ID (for fast database lookup) with a slug (for readability). The pattern is popular because:
- Lookup is O(1) by ID, no need to query on the slug column.
- The slug portion can change without breaking the URL (just redirect the old slug to the new one).
- Stack Overflow, YouTube, and many large sites use this pattern.
A pure slug URL (/blog/what-is-a-slug) is cleaner but requires the slug column to be indexed and unique in your database.
Slugs in popular frameworks
Next.js (dynamic routes):
app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return <article>Post: {params.slug}</article>;
}
Django:
from django.db import models
from django.utils.text import slugify
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super().save(*args, **kwargs)
WordPress calls them "permalinks" and generates them automatically from the post title, with a settings page to choose the URL structure.
Generate a slug online
If you need to quickly turn a title or phrase into a clean slug, use the Slugify tool. Paste any text — including accented characters and special symbols — and get a URL-ready slug instantly. Runs entirely in your browser.
FAQ
Q: Hyphens or underscores in slugs?
Hyphens. Google explicitly recommended hyphens over underscores for word separation in URLs back in 2009, and that guidance has never changed. my-blog-post is treated as three words; my_blog_post is treated as one.
Q: Should slugs include stop words like "a", "the", "and"?
It depends. Shorter slugs are generally better, so many sites strip stop words: "What is a Slug" becomes what-is-slug. But if the stop word changes meaning (e.g., "to be or not to be"), keep it. There's no universal rule — pick a consistent approach and stick to it.
Q: Can slugs contain numbers?
Yes. top-10-css-tips, python-3-guide, iphone-15-review are all fine. Numbers are valid URL characters and don't need encoding.
Q: What about non-Latin scripts (Arabic, Chinese, Japanese)?
You have two options: transliterate to Latin characters (café → cafe, 東京 → tokyo) or keep the original Unicode characters URL-encoded. Most English-language sites transliterate. Sites targeting specific language markets often use native-script slugs, since they're more readable to the target audience and native-language search engines weight them accordingly.
Q: How long should a slug be? As short as possible while remaining descriptive. 3–6 words is a common guideline. Very long slugs can look spammy to users and may get truncated in some displays. Keep the most important keywords near the beginning.