Toolmingo
Guides6 min read

What Is a Slug in Web Development?

Learn what a URL slug is, why slugs matter for SEO and usability, and how to generate clean slugs in JavaScript, Python, Go, and PHP.

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-Post and /my-post can 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 hyphensmy--post is 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:

  1. Lowercase the string.
  2. Transliterate or remove accented/Unicode characters (cafécafe).
  3. Replace any character that isn't a letter, digit, or hyphen with a hyphen.
  4. Collapse multiple consecutive hyphens into one.
  5. 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.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools