Introduction

A good URL should be easy to read, easy to share, and meaningful for both users and search engines. Compare these two URLs:

/posts/15/

and

/posts/django-slugs-and-clean-urls/

The second one is much clearer. It tells the user what the page is about before even opening it. This is where slugs come in.

In Django, a slug is a short, URL-friendly string, usually based on a title. Slugs are commonly used to create clean and SEO-friendly URLs for blog posts, products, categories, courses, and many other objects.

In this tutorial, you will learn how slugs work in Django, how to create them, how to use them in URLs and views, and how to handle common problems such as duplicate slugs.

What You Will Learn

By the end of this tutorial, you will understand:

  • what a slug is
  • why slugs are useful
  • how to add a slug field to a model
  • how to generate slugs automatically
  • how to use slugs in URLs
  • how to retrieve objects using slugs
  • how to avoid duplicate slugs
  • best practices for clean URLs in Django

Prerequisites

Before starting, you should already know:

  • Django models
  • views and URLs
  • generic views like DetailView
  • basic templates

1. What Is a Slug?

A slug is a URL-safe text string.

Example:

  • Title: Introduction to Django Forms
  • Slug: introduction-to-django-forms

A slug usually contains:

  • lowercase letters
  • numbers
  • hyphens

It should not contain:

  • spaces
  • special characters
  • accents or symbols in most cases

Django provides a built-in field for this: SlugField.

2. Why Use Slugs?

Slugs are useful because they make URLs:

  • more readable
  • easier to remember
  • better for SEO
  • easier to share
  • more professional

For example:

/products/42/

is less clear than:

/products/wireless-keyboard/

3. Adding a Slug Field to a Model

Let us start with a simple blog post model.

models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    content = models.TextField()

    def __str__(self):
        return self.title

Explanation

  • title stores the post title
  • slug stores the clean URL version
  • unique=True ensures no two posts have the same slug

After adding the field, run:

python manage.py makemigrations
python manage.py migrate

4. Creating Slugs Manually

You can enter slugs manually in Django admin or forms.

Example:

  • title: Django Models Guide
  • slug: django-models-guide

This works, but it is not ideal because users may forget to enter the slug or enter it incorrectly.

That is why automatic slug generation is often better.

5. Generating Slugs Automatically with slugify

Django provides a utility called slugify.

Example

from django.utils.text import slugify

title = "Django Slugs and Clean URLs"
print(slugify(title))

Output:

django-slugs-and-clean-urls

It converts text into a URL-friendly slug.

6. Auto-Generating Slugs in save()

A common approach is to generate the slug automatically when saving the object.

models.py

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, blank=True)
    content = models.TextField()

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
        super().save(*args, **kwargs)

How it works

  • if the slug is empty, Django creates it from the title
  • if the slug already exists, Django keeps it
  • blank=True allows the form/admin to leave it empty

7. Using Slugs in URLs

Now that each post has a slug, we can use it in the URL.

urls.py

from django.urls import path
from .views import post_detail

urlpatterns = [
    path('posts/<slug:slug>/', post_detail, name='post_detail'),
]

Explanation

  • <slug:slug> means Django expects a slug in the URL
  • the captured value is passed to the view as slug

Example URL:

/posts/django-slugs-and-clean-urls/

8. Retrieving Objects by Slug in a Function-Based View

views.py

from django.shortcuts import render, get_object_or_404
from .models import Post

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug)
    return render(request, 'blog/post_detail.html', {'post': post})

Explanation

  • Django receives the slug from the URL
  • get_object_or_404() searches for a matching post
  • if not found, Django returns a 404 page

9. Using Slugs with DetailView

Slugs work very well with class-based views too.

views.py

from django.views.generic import DetailView
from .models import Post

class PostDetailView(DetailView):
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'
    slug_field = 'slug'
    slug_url_kwarg = 'slug'

urls.py

from django.urls import path
from .views import PostDetailView

urlpatterns = [
    path('posts/<slug:slug>/', PostDetailView.as_view(), name='post_detail'),
]

Explanation

  • slug_field = 'slug' tells Django which model field to use
  • slug_url_kwarg = 'slug' tells Django which URL parameter contains the slug

10. Adding Links in Templates

Once your URLs use slugs, links should also use slugs.

Example in post_list.html

{% for post in posts %}
    <h2>
        <a href="{% url 'post_detail' post.slug %}">
            {{ post.title }}
        </a>
    </h2>
{% endfor %}

Now each post link points to a clean URL.

11. The Problem of Duplicate Slugs

Suppose you create two posts with the same title:

  • Django Tutorial
  • Django Tutorial

Both would generate:

django-tutorial

 

But if slug is unique, the second one will fail.

So you need a way to make slugs unique.

12. Making Slugs Unique

A simple approach is to add a number when the slug already exists.

Example

  • first post → django-tutorial
  • second post → django-tutorial-1
  • third post → django-tutorial-2

models.py

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, blank=True)
    content = models.TextField()

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.slug:
            base_slug = slugify(self.title)
            slug = base_slug
            counter = 1

            while Post.objects.filter(slug=slug).exists():
                slug = f"{base_slug}-{counter}"
                counter += 1

            self.slug = slug

        super().save(*args, **kwargs)

Explanation

  • start with the basic slug
  • check if it already exists
  • if yes, append -1, -2, etc.
  • keep trying until a unique slug is found

13. Using get_absolute_url() with Slugs

A very useful Django convention is get_absolute_url().

Example

from django.db import models
from django.urls import reverse
from django.utils.text import slugify

class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True, blank=True)
    content = models.TextField()

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('post_detail', kwargs={'slug': self.slug})

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
        super().save(*args, **kwargs)

Why it is useful

  • generic views like CreateView and UpdateView can redirect automatically
  • you can call post.get_absolute_url()
  • it centralizes the object’s URL logic inside the model

14. Real Example: Blog Post Workflow

models.py

from django.db import models
from django.urls import reverse
from django.utils.text import slugify

class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True, blank=True)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('post_detail', kwargs={'slug': self.slug})

    def save(self, *args, **kwargs):
        if not self.slug:
            base_slug = slugify(self.title)
            slug = base_slug
            counter = 1

            while Post.objects.filter(slug=slug).exists():
                slug = f"{base_slug}-{counter}"
                counter += 1

            self.slug = slug

        super().save(*args, **kwargs)

views.py

from django.views.generic import ListView, DetailView
from .models import Post

class PostListView(ListView):
    model = Post
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'

class PostDetailView(DetailView):
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'
    slug_field = 'slug'
    slug_url_kwarg = 'slug'

urls.py

from django.urls import path
from .views import PostListView, PostDetailView

urlpatterns = [
    path('posts/', PostListView.as_view(), name='post_list'),
    path('posts/<slug:slug>/', PostDetailView.as_view(), name='post_detail'),
]

post_list.html

<h1>Blog Posts</h1>

{% for post in posts %}
    <h2>
        <a href="{% url 'post_detail' post.slug %}">
            {{ post.title }}
        </a>
    </h2>
{% endfor %}

post_detail.html

<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>

This gives you a complete clean-URL blog structure.

15. Slugs in the Django Admin

If you register the model in admin:

from django.contrib import admin
from .models import Post

admin.site.register(Post)

you can manually edit slugs there.

A more user-friendly admin setup is:

from django.contrib import admin
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ['title', 'slug']
    prepopulated_fields = {'slug': ('title',)}

What this does

  • when typing the title in admin, Django auto-fills the slug field
  • this saves time and reduces mistakes

16. Best Practices for Slugs

Keep slugs short and meaningful

Good:

django-class-based-views

Less ideal:

this-is-a-very-long-post-title-about-learning-django-class-based-views-in-2026

Use unique slugs

Avoid conflicts between objects.

Do not change slugs too often

Changing slugs can break old links and hurt SEO.

Use slugs for public content

They are especially useful for blog posts, products, categories, courses, and public profiles.

17. Common Mistakes

Forgetting unique=True

Without it, two objects may get the same slug.

Forgetting blank=True when auto-generating

If the slug is created in save(), forms/admin usually need blank=True.

Not handling duplicates

If two titles are the same, save may fail.

Using spaces or special characters manually

Slugs should remain URL-safe.

Retrieving by id while URL uses slug

Make sure your view and URL match.

Wrong:

post = get_object_or_404(Post, id=slug)

Correct:

post = get_object_or_404(Post, slug=slug)

18. Slugs vs IDs

Sometimes developers use both.

Example:

/posts/15/django-slugs-and-clean-urls/

This combines:

  • the database ID for uniqueness
  • the slug for readability

This is common in larger applications, but for beginners, using just the slug is simpler and easier to understand.

19. Mini Project Example: Product Pages

Let us imagine an online store.

models.py

from django.db import models
from django.urls import reverse
from django.utils.text import slugify

class Product(models.Model):
    name = models.CharField(max_length=150)
    slug = models.SlugField(unique=True, blank=True)
    description = models.TextField()
    price = models.DecimalField(max_digits=8, decimal_places=2)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('product_detail', kwargs={'slug': self.slug})

    def save(self, *args, **kwargs):
        if not self.slug:
            base_slug = slugify(self.name)
            slug = base_slug
            counter = 1

            while Product.objects.filter(slug=slug).exists():
                slug = f"{base_slug}-{counter}"
                counter += 1

            self.slug = slug

        super().save(*args, **kwargs)

urls.py

path('products/<slug:slug>/', product_detail, name='product_detail')

Now product pages become:

/products/wireless-mouse/
/products/gaming-keyboard/
/products/usb-c-hub/

These are much better than numeric-only URLs.

20. Summary

In this tutorial, you learned that:

  • a slug is a URL-friendly text string
  • slugs create clean and SEO-friendly URLs
  • Django provides SlugField and slugify
  • slugs can be auto-generated in save()
  • slugs can be used in function-based views and generic views
  • duplicate slugs must be handled properly
  • get_absolute_url() works very well with slugs
  • clean URLs improve user experience and project quality

 

21. Mini Quiz

1. What is a slug?

A. A database password
B. A URL-friendly string
C. A template tag
D. A file path

2. Which Django field is used for slugs?

A. TextField
B. CharField
C. SlugField
D. URLField

3. Which Django utility converts text into a slug?

A. reverse()
B. slugify()
C. clean_url()
D. pathify()

4. Why is blank=True often used with slug fields?

A. To make the field invisible
B. To allow auto-generation when the form leaves it empty
C. To delete old slugs
D. To shorten URLs

5. Which URL pattern correctly captures a slug?

A. <int:id>
B. <str:name>
C. <slug:slug>
D. <url:path>

22. What Comes Next?

The next ideal tutorial is:

Tutorial: File Uploads in Django
Subject: uploading images and documents, configuring media files, and handling user-uploaded content.