Django slug field generator: SlugField, slugify, and unique constraints
Django ships everything you need to build clean URL slugs without a third-party package: a model field, a utility function, an admin helper, and a Unicode mode. The pieces fit together if you wire them in the right order.
SlugField is the column type, slugify is the function
In your model, store the slug in a models.SlugField(max_length=140). It is a VARCHAR with database validation that the value matches the slug regex (letters, digits, hyphens, underscores). It does not generate the slug for you. That is a separate step.
To turn a title into a slug string, call django.utils.text.slugify:
from django.utils.text import slugify
slugify("Hello, World! 2026") # "hello-world-2026"
slugify lowercases, strips non-word characters, collapses whitespace into single hyphens, and trims edges. It is deterministic and safe to call repeatedly.
allow_unicode for non-ASCII titles
By default slugify runs through unicodedata.normalize('NFKD', ...) and discards anything that is not ASCII. A title like "Cómo crear" becomes "como-crear", which is fine for Spanish but loses information for Greek, Cyrillic, Arabic, Chinese.
If you want to keep the original characters, pass allow_unicode=True:
slugify("Καλημέρα κόσμε", allow_unicode=True) # "καλημέρα-κόσμε"
Pair it with SlugField(allow_unicode=True) so the field validator accepts the same character set the function emits. If the model field is ASCII-only and you write a Unicode slug, you get a validation error at full_clean or at admin save time.
prepopulated_fields in admin and the auto-update gotcha
In ModelAdmin, set prepopulated_fields = {"slug": ("title",)} and the admin form will populate the slug field with JavaScript as you type the title. It uses a port of slugify in the browser, so the result matches what the server would compute.
The trap: prepopulation only fires while the slug field is empty. Once you save the row the JS stops touching it, which is the right behaviour for published content. If you want the slug to follow title edits forever, you have to override save and recompute, but doing that on a published post breaks every existing inbound link. Most CMS-style apps lock the slug after the first save and accept that retitling means a manual slug edit and a redirect.
Uniqueness with unique_for_date and save overrides
SlugField(unique=True) puts a database constraint on the column and rejects duplicates. That is what most blogs want. If you allow the same slug across different dates (a daily-post pattern, like a status-update blog) use unique_for_date="published_at" to scope uniqueness to a single calendar day.
For the common "append a number on collision" behaviour, override save:
def save(self, *args, **kwargs):
if not self.slug:
base = slugify(self.title, allow_unicode=True)
candidate = base
n = 1
while Post.objects.filter(slug=candidate).exclude(pk=self.pk).exists():
n += 1
candidate = f"{base}-{n}"
self.slug = candidate
super().save(*args, **kwargs)
This pattern is fine at moderate write volume. At very high concurrency you race between the SELECT and the INSERT and end up with a unique constraint violation; wrap it in a try/except on IntegrityError and retry, or use a sequence column instead.
Working example
python# blog/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(max_length=140, unique=True, allow_unicode=True)
body = models.TextField()
published_at = models.DateTimeField(null=True, blank=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title, allow_unicode=True)
super().save(*args, **kwargs)
# blog/admin.py
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
list_display = ("title", "slug", "published_at") Just need the result?
When you are drafting URLs in a content calendar before the post lives in Django, the browser-based slug generator at aldeacode.com gives the same result as django.utils.text.slugify, runs entirely on your machine, and lets you batch-plan a month of slugs in a spreadsheet without spinning up a shell.
Open URL Slug Generator →Frequently asked questions
Should I let signals update the slug automatically?
No. A post_save signal that recomputes the slug on every title edit is a recipe for broken URLs. Compute the slug on first save (or in the admin via prepopulated_fields) and only touch it afterwards through an explicit migration with redirects in place.
Why does my slug end up empty for some titles?
slugify strips characters that have no ASCII fold. A title made entirely of emoji or punctuation slugifies to an empty string. Guard with a fallback: self.slug = slugify(self.title) or f'post-{self.pk}'. Run that after the first save so pk is populated.
Can I use slugify outside a model, in a script or a view?
Yes. django.utils.text.slugify is a plain function with no settings dependency. You can call it from management commands, Celery tasks, DRF serializers, anywhere. allow_unicode is the only parameter most code needs.