Start Every Django Project with a Custom User Model

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.

The Inevitable Change

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.

The High Cost of Waiting

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 Simple Two Minute Solution

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.

What You Gain

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