When you start a new Django project you have many decisions to make. Most of them can be changed later. You can swap out a template engine or change your caching strategy. But one decision is nearly irreversible. That is the user model.
The default user model that comes with django.contrib.auth
is tempting. It works out of the box and has fields for username email and password. For a small project this seems fine. The problem is that projects grow and their requirements change. Using the default user model is a bet that your future needs will never differ from what Django’s core team decided years ago. This is a bad bet.
Sooner or later you will want to change something about your users. Maybe you want to store a profile picture or a phone number. Perhaps your business logic requires a subscription tier or an API key to be stored directly on the user object.
A more common scenario is wanting to use an email address as the primary identifier instead of a username. This is standard practice in modern web applications but the default Django user model is built around the username.
If you use the default User
model you cannot add fields to it directly. Your only option is to create a separate Profile
model with a one to one relationship. This works but it’s clumsy. You will constantly be writing code that joins the two models. A query for a user’s avatar will look like user.profile.avatar_url
. This extra layer of indirection adds complexity everywhere. It also leads to performance problems which you can read more about in A Simple Fix for Slow Django Queries. It is cleaner and more efficient to have one canonical User
object.
Switching to a custom user model after a project has launched and has real users is painful. It is one of the most difficult data migrations you can perform in Django.
All of your existing foreign keys to the default User
model need to be updated. This includes tables created by Django’s own apps like admin and sessions and any third party apps you are using. You have to write a complex data migration script that moves data from the old auth_user
table to your new custom user table while carefully preserving all relationships.
It is a high risk operation. If you get it wrong you could corrupt your user data or cause significant downtime. Many experienced developers will tell you stories about the weekend they lost to a user model migration. The cost of this mistake is not just a few hours of work. It is a major engineering project that pulls you away from building features your users want.
The good news is that avoiding this entire problem takes less than two minutes at the start of your project. The solution is to tell Django you will be providing your own user model right from the beginning.
Here is how you do it.
First create a new app to hold your user model. It is good practice to keep your core models in their own app.
python manage.py startapp users
Next open the users/models.py
file. Instead of creating a model from scratch you will inherit from Django’s AbstractUser
. This class provides all the same fields and functionality as the default User
model but it’s a base class you can extend.
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
pass
That is all the code you need to write for now. Even this empty model is now yours. You can add new fields to it whenever you want in the future just like any other model.
The final step is to tell Django to use this model instead of its default one. Open your settings.py
file and add this line at the bottom.
AUTH_USER_MODEL = 'users.User'
You must also add your new users
app to the INSTALLED_APPS
list in settings.py
. Make sure it’s listed there so Django can find your new model.
This must all be done before you run your first python manage.py migrate
command. If you have already run migrate you should delete your database file and start over. It is that important to get this right from the beginning.
By taking these simple steps you have future proofed your application. You are no longer locked into the design decisions of the default user model.
Want to add a date of birth? Just add a DateField
to your User
model. Want to use email as the username? You can set the USERNAME_FIELD
attribute to 'email'
.
from django.contrib.auth.models import AbstractUser from django.db import models
class User(AbstractUser): username = None email = models.EmailField('email address', unique=True)
USERNAME_FIELD = 'email' REQUIRED_FIELDS = []
Your User
model is now a first class citizen in your project. It is not some special unchangeable thing from the framework. It is your code and you have complete control over it. This simplicity and control will save you and your team countless hours down the road.
The effort is minimal and the reward is enormous. It’s a trade with nearly infinite leverage. Making this one small change at the start of every project is one of the hallmarks of an experienced Django developer.
Try writing down a small decision you made early in a project that saved you time later.
— Rishi Banerjee
September 2025