Django urls.py: Routing, URL Patterns, and Namespacing

A beginner-friendly guide to Django’s urls.py, explaining how URL routing connects requests to views, how to define static and dynamic URL patterns, and how to organize routes with include(). The article also covers URL names, reverse URL resolution, common routing mistakes, best practices, and namespacing for avoiding conflicts in larger Django projects.

Django urls.py: Routing, URL Patterns, and Namespacing

In a Django project, urls.py is one of the most important files because it defines how incoming web requests are routed to views. When a user types a URL into the browser, clicks a link, submits a form, or sends an API request, Django needs to decide which piece of code should handle that request. This decision is made through the URL configuration, commonly called the URLconf.

The urls.py file acts as a map between URLs and views. It tells Django, “When this URL pattern is requested, call this view function or class.” Without a properly configured urls.py, Django would not know how to connect browser requests to the logic written in views.

Understanding urls.py helps to properly organize routes, connect multiple apps, create dynamic URLs, avoid duplicate URL names, and make templates more maintainable. This article explains how Django’s URL routing works, how to define URL patterns, how to pass parameters through URLs, and how namespacing helps manage larger projects.

What Is urls.py in Django?

A Django project usually has a main urls.py file inside the project folder. For example, if a project is called mysite, the main URL configuration may be located at:

mysite/urls.py

This file contains the root URL patterns for the whole project. A simple project-level urls.py might look like this:

python

1
2
3
4
5
6
7
8
from django.contrib import admin
from django.urls import path
from blog import views

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", views.home, name="home"),
]

In this example, two URL routes are defined. The first route sends requests beginning with admin/ to Django’s built-in admin site. The second route sends requests for the homepage to the home view in the blog app.

The variable urlpatterns is a list of URL patterns. Django checks this list from top to bottom and uses the first pattern that matches the requested URL.

The Role of URL Routing

URL routing is the process of matching an incoming URL to the correct view. For example, assume a user visits:

https://example.com/about/

Django does not automatically know what /about/ means. The route must be defined in urls.py:

python

1
2
3
4
5
6
from django.urls import path
from . import views

urlpatterns = [
    path("about/", views.about, name="about"),
]

Here, the URL pattern "about/" is connected to the about view. When the user visits /about/, Django calls views.about(request) and returns the response generated by that view.

This routing system keeps URL structure separate from business logic. Views handle what happens, while URL patterns define where it happens.

The path() Function

The most commonly used function in Django URL configuration is path(). It is imported from django.urls and is used to define simple and readable URL patterns.

The basic syntax is:

path(route, view, kwargs=None, name=None)

The route is the URL pattern as a string. The view is the function or class-based view that should handle the request. The optional kwargs argument can pass extra information to the view. The name argument gives the route a reusable identifier.

For example:

python

1
path("contact/", views.contact, name="contact")

This pattern means that when a user visits /contact/, Django should call the contact view. The name "contact" can later be used in templates, redirects, and reverse URL lookups.

Dynamic URL Patterns

Many web applications need URLs that contain changing values. For example, a blog website may need a different URL for each post:

/blog/1/
/blog/2/
/blog/3/

Instead of writing a separate URL pattern for every blog post, Django allows dynamic URL segments using path converters.

python

1
path("blog/<int:post_id>/", views.post_detail, name="post_detail")

In this pattern, <int:post_id> captures an integer from the URL and passes it to the view as an argument.

The view might look like this:

python

1
2
def post_detail(request, post_id):
    return HttpResponse(f"Viewing blog post {post_id}")

If a user visits /blog/7/, Django calls:

post_detail(request, post_id=7)

This makes URL routing powerful and flexible.

Django provides several built-in path converters:

  • The str converter matches any non-empty string except a slash.
  • The int converter matches integers.
  • The slug converter matches letters, numbers, hyphens, and underscores.
  • The uuid converter matches a UUID value.
  • The path converter matches a string that may include slashes.

A slug-based URL is common for articles:

path("articles/<slug:slug>/", views.article_detail, name="article_detail")

This produces readable URLs such as:

/articles/django-url-routing-guide/

Using include() for App-Level URLs

As a Django project grows, placing every URL pattern in the project-level urls.py can become messy. Django encourages developers to create separate urls.py files inside individual apps and connect them using include().

For example, a project may have a blog app with its own URL configuration:

blog/urls.py

Inside blog/urls.py:

python

1
2
3
4
5
6
7
from django.urls import path
from . import views

urlpatterns = [
    path("", views.post_list, name="post_list"),
    path("<slug:slug>/", views.post_detail, name="post_detail"),
]

Then the project-level urls.py includes the app URLs:

python

1
2
3
4
5
6
7
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("blog/", include("blog.urls")),
]

Now, when a user visits /blog/, Django looks inside blog.urls. When the user visits /blog/django-routing/, Django also uses the patterns defined in blog.urls.

This approach keeps routing modular.

Each app manages its own URLs, while the main project file simply connects apps together.

URL Names

URL names are one of the most useful parts of Django routing. Instead of hardcoding URLs in templates or views, developers can refer to routes by name.

For example:

path("about/", views.about, name="about")

In a Django template, this route can be linked like this:

html

1
<a href="{% url 'about' %}">About</a>

This is better than writing:

html

1
<a href="/about/">About</a>

Using URL names makes the application easier to maintain. If the actual URL changes from /about/ to /company/about/, the template does not need to change as long as the route name remains the same.

URL names are also useful in views:

python

1
2
3
4
from django.shortcuts import redirect

def contact_success(request):
    return redirect("about")

Django resolves the name "about" into the correct URL.

Always refer to a url by its name. Don't hardcode routes in templates or views to avoid conflicts if the routing changes.

Reverse URL Resolution

Django’s ability to generate URLs from route names is called reverse URL resolution. Instead of manually constructing URLs, developers ask Django to reverse a route name into a URL.

In Python code, this can be done using reverse():

python

1
2
3
from django.urls import reverse

url = reverse("post_detail", kwargs={"slug": "django-urls"})

If the URL pattern is:

path("blog/<slug:slug>/", views.post_detail, name="post_detail")

Then Django returns:

/blog/django-urls/

Reverse URL resolution reduces errors and improves maintainability. It also allows developers to change URL structures without rewriting links throughout the project.

Namespacing in Django URLs

Namespacing is an important concept in Django URL configuration, especially in larger projects. A namespace allows multiple apps to use the same URL name without causing conflicts.

For example, imagine a project with two apps: blog and shop. Both apps may have a detail page:

blog/urls.py

path("<slug:slug>/", views.post_detail, name="detail")

shop/urls.py path("<slug:slug>/", views.product_detail, name="detail")

Both URL patterns are named "detail". Without namespacing, Django may not know which "detail" route is meant when reversing a URL.

Namespacing solves this by grouping URL names under an app-specific label.

App Namespaces

To create an app namespace, define app_name inside the app’s urls.py file.

For example, in blog/urls.py:

python

1
2
3
4
5
6
7
8
9
from django.urls import path
from . import views

app_name = "blog"

urlpatterns = [
    path("", views.post_list, name="list"),
    path("<slug:slug>/", views.post_detail, name="detail"),
]

In shop/urls.py:

python

1
2
3
4
5
6
7
8
9
from django.urls import path
from . import views

app_name = "shop"

urlpatterns = [
    path("", views.product_list, name="list"),
    path("<slug:slug>/", views.product_detail, name="detail"),
]

Now Django can distinguish between the blog detail page and the shop detail page.

In a template, the blog detail URL can be written as:

html

1
<a href="{% url 'blog:detail' slug=post.slug %}">Read post</a>

The shop detail URL can be written as:

html

1
<a href="{% url 'shop:detail' slug=product.slug %}">View product</a>

The format is:

namespace:url_name

So blog:detail means the detail URL inside the blog namespace.

Including Namespaced URLs

The project-level urls.py usually includes namespaced app URLs like this:

python

1
2
3
4
5
6
from django.urls import path, include

urlpatterns = [
    path("blog/", include("blog.urls")),
    path("shop/", include("shop.urls")),
]

Because each app’s urls.py defines app_name, Django recognizes the namespace automatically.

However, it is cleaner to specify a namespace explicitly while including URLs:

path("articles/", include(("blog.urls", "blog"), namespace="articles"))

This creates an instance namespace. Instance namespaces are useful when the same app is included more than once with different URL prefixes.

For example:

python

1
2
3
4
urlpatterns = [
    path("public-blog/", include(("blog.urls", "blog"), namespace="public_blog")),
    path("staff-blog/", include(("blog.urls", "blog"), namespace="staff_blog")),
]

Now the same app can be referenced with different namespaces:

html

1
2
{% url 'public_blog:list' %}
{% url 'staff_blog:list' %}

This pattern is less common in beginner projects but is useful in advanced applications.

Common Mistakes in urls.py

One common mistake is forgetting the trailing slash. Django projects usually use trailing slashes in URL patterns, such as "about/" instead of "about". If APPEND_SLASH is enabled, Django may redirect users automatically, but it is still best to be consistent.

Another mistake is using duplicate URL names without namespaces. This can cause confusing reverse URL errors or cause Django to resolve the wrong route. Namespaces are the best solution when multiple apps have similar route names.

A third mistake is placing too many routes in the project-level urls.py. This makes the project harder to maintain. A cleaner approach is to give each app its own urls.py and use include().

Developers also sometimes hardcode URLs in templates. This should be avoided. The {% url %} template tag and reverse() function are safer and more maintainable.

Best Practices for Django URL Configuration

A good Django URL structure should be readable, consistent, and organized. Each app should usually have its own urls.py file. URL names should be meaningful and predictable. Namespaces should be used when an app has its own routing system or when duplicate URL names are possible.

It is also wise to use slugs for public-facing content when readability matters. For example, /articles/django-url-guide/ is more user-friendly than /articles/42/. However, integer IDs are still useful for internal pages or simple applications.

Routes should describe resources clearly. For example, a blog app might use:

python

1
2
3
4
5
urlpatterns = [
    path("", views.post_list, name="list"),
    path("<slug:slug>/", views.post_detail, name="detail"),
    path("category/<slug:category_slug>/", views.category_posts, name="category"),
]

This structure is easy for users and developers to understand.

Django’s urls.py file is the routing center of a Django project. It connects incoming URLs to views, organizes application routes, supports dynamic URL parameters, and allows developers to build maintainable URL structures. The path() function is used to define URL patterns, while include() helps divide routing across multiple apps. URL names make links reusable, and reverse URL resolution allows Django to generate URLs automatically. Namespacing is especially important in larger projects because it prevents conflicts between URL names used by different apps. A well-designed URL configuration makes a Django project easier to navigate, maintain, and scale. By understanding urls.py, URL names, dynamic routes, include(), and namespacing, developers gain strong control over how users move through a Django application.

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.