Setting Up Static and Dynamic Sitemaps in Django
Learn how to create static and dynamic sitemaps in Django using the built-in sitemap framework, including model-backed URLs, static views, location(), and common routing mistakes to avoid.
A sitemap is an XML file that tells search engines which URLs exist on your site and, optionally, how often they change, how important they are, and when they were last modified. Django includes a built-in sitemap framework for generating these XML files from Python classes instead of maintaining them manually.
Django sitemaps usually fall into two categories:
- Static sitemaps for pages that are not backed by a database model, such as your homepage, about page, contact page, or legal pages.
- Dynamic sitemaps for pages generated from model instances, such as blog posts, categories, projects, products, or resources.
This article shows how to set up both.
1. Enable Django’s sitemap framework
First, add the sitemap framework to INSTALLED_APPS:
settings.py
⧉
1 2 3 4 | |
2. Create a sitemaps.py file
You can place this file wherever it makes sense. A common approach is to put it in your project config app:
project/
settings.py
urls.py
sitemaps.py
Or inside individual apps:
apps/
blog/
models.py
sitemaps.py
For a small or medium project, a central sitemaps.py is often easiest.
Static Sitemaps
Static pages are views that exist in your URLconf but are not represented by database rows.
Examples:
urls.py
⧉
1 2 3 4 5 | |
To add these pages to your sitemap, create a Sitemap class that returns URL names from items() and resolves them with reverse() in location().
sitemaps.py
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
If your URLs are namespaced, include the namespace:
⧉
1 2 3 4 5 6 | |
Dynamic Sitemaps
Dynamic sitemaps are for model-backed pages.
For example:
apps/blog/models.py
⧉
1 2 3 4 5 6 7 8 | |
Suppose your URL pattern looks like this:
apps/blog/urls.py
⧉
1 2 3 4 5 6 7 | |
Then your dynamic sitemap can look like this:
sitemaps.py
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
The items() method returns the objects that belong in the sitemap. Django passes each object to methods like location(), lastmod(), changefreq(), and priority().
Alternative: use get_absolute_url()
Instead of defining location() in the sitemap class, you can define get_absolute_url() on the model:
apps/blog/models.py
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Then your sitemap changes to:
⧉
1 2 3 4 5 6 7 8 9 | |
If no location() method is provided, Django calls get_absolute_url() on each object returned by items().
A Reusable Base Sitemap for Active Models
If several models share the same pattern, create a base sitemap class:
sitemaps.py
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Then subclass it:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | |
This gives you one shared rule for filtering active content and setting lastmod, while still letting each model define its own URL structure.
Register the Sitemaps in urls.py
Once your sitemap classes exist, register them in your root URLconf.
urls.py
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
Now visit:
/sitemap.xml
Django will generate an XML sitemap dynamically.
The sitemaps dictionary maps a short section name, such as "posts" or "static", to a Sitemap class or instance.
Common Mistake: get_absolute_url() in the Wrong Class
This is wrong:
⧉
1 2 3 4 5 | |
Why? Because Django does not call get_absolute_url() on the sitemap class. It calls it on the model instance.
So either put it on the model:
⧉
1 2 3 4 5 | |
Or define location() on the sitemap:
⧉
1 2 3 4 5 | |
Common Mistake: URL Keyword Mismatches
If your URL pattern is:
⧉
1 2 3 4 5 | |
Then your reverse() call must use:
⧉
1 2 3 4 5 | |
Not:
⧉
1 2 3 4 5 | |
The Python model field may be called sub_category, but the URL parameter is called subcategory. Django’s reverse() matches the URL pattern keyword, not the model field name.
Optional: Sitemap Index for Larger Sites
For larger sites, you can generate a sitemap index. This creates a main sitemap.xml that points to separate sitemap files like:
/sitemap-posts.xml /sitemap-categories.xml /sitemap-static.xml
Example:
urls.py
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Django’s sitemap framework supports sitemap indexes that reference one sitemap file per section. This is especially useful when you have many URLs; the sitemap protocol limit is 50,000 URLs per sitemap, and Django’s default sitemap limit is aligned with that limit.
Complete Example sitemaps.py
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
urls.py
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Final Checklist:
Before calling the sitemap finished, verify these points:
[] "django.contrib.sitemaps" is in INSTALLED_APPS [] /sitemap.xml is registered in the root urls.py [] Static pages use reverse() inside location() [] Dynamic model objects either define get_absolute_url() or the sitemap defines location() [] URL kwargs match the URL pattern names exactly [] QuerySets filter out inactive, draft, private, or unpublished objects [] lastmod returns a datetime/date where possible [] Large sites use a sitemap index
That gives search engines one clean sitemap endpoint while keeping your sitemap logic explicit, testable, and close to your URL design.
Join the Newsletter
Practical insights on Django, backend systems, deployment, architecture, and real-world development — delivered without noise.
Get updates when new guides, learning paths, cheat sheets, and field notes are published.
No spam. Unsubscribe anytime.
There is no third-party involved so don't worry - we won't share your details with anyone.