0. Introduction
The user system is one of the most important parts of a Django project. Almost every serious application needs users in some form: registration, login, profiles, permissions, dashboards, ownership of content, subscriptions, and account settings. Django already provides a built-in User model, and for many small projects that is enough. But in real-world development, very soon you may want to store extra information, change the login field, or control authentication behavior more deeply. That is where the custom user model becomes essential.
This tutorial explains the custom user model in Django with deep detail. We will not only see how to create it, but also why it exists, when to use it, what common mistakes to avoid, and how it affects the architecture of your project. This topic is extremely important because choosing the user model early can save you from major migration problems later.
1. Why the User Model Matters So Much
In a Django project, the user model is more than just a table for usernames and passwords. It becomes the center of many other relationships.
For example, many models may connect to the user:
- blog posts belong to a user
- comments are written by a user
- orders are placed by a user
- profiles extend a user
- notifications belong to a user
- permissions are assigned to a user
This means the user model is often one of the first and most central database structures in the project. If you make the wrong design choice early, changing it later can become painful because so many other models may already depend on it.
That is why Django developers often repeat this advice: if you think you may need a custom user model, create it at the beginning of the project. In practice, that often means: create one from the start even if it looks simple.
2. Django’s Default User Model
Django provides a built-in model in:
django.contrib.auth.models.UserIt already includes useful fields like:
usernamefirst_namelast_nameemailpasswordis_staffis_superuseris_activelast_logindate_joined
This model is good for many simple cases. Django’s authentication system, admin, permissions, and sessions already know how to work with it.
So naturally, a beginner may ask: if Django already gives a user model, why should I make a custom one?
Because the default model is fixed. If later you need structural changes, things get harder.
3. The Main Reason to Use a Custom User Model
A custom user model gives you control.
You may want to:
- log in with email instead of username
- add fields like phone number, country, birth date, or role
- remove the username field
- use a UUID instead of integer ID
- adapt the user structure to your business logic
- make the model future-proof from day one
The deepest reason is not only “I want more fields.” It is that your project may need a user identity system that does not perfectly match Django’s default assumptions.
Once you understand that, the custom user model becomes less of an advanced trick and more of a good architectural decision.
4. Two Ways People Extend the User System
This is one of the most important distinctions.
There are two common strategies:
Strategy 1: Keep Django’s default User and create a separate Profile model
Example:
- Django
Userstores auth-related fields Profilestores extra user information
This is simple and common.
Strategy 2: Create a full custom user model
Example:
- one model replaces Django’s default
User - it can contain custom fields and login behavior
This is more flexible and more powerful.
A profile model is often enough if you only want to add a few extra fields. But a full custom user model is better when you want to control authentication identity itself, such as using email as the main login field.
5. When a Profile Model Is Enough
A profile model may be enough when:
- you are happy with Django’s default username-based auth
- you only want extra optional fields
- you want a simpler approach
- the project is already built on the default user model
For example:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone = models.CharField(max_length=20, blank=True)
city = models.CharField(max_length=100, blank=True)This is a valid design. It keeps authentication separate from profile data.
But it does not change the core user identity model.
6. When a Full Custom User Model Is Better
A full custom user model is better when:
- you want email login instead of username
- you want the main user table to contain your custom fields
- you want to remove or redefine the username field
- you want full flexibility from the beginning
- you want to avoid regrets later
In modern projects, many developers prefer starting with a custom user model immediately, even if it looks close to the default one. The reason is simple: it avoids future migration pain if requirements change.
This is often the best long-term decision.
7. The Two Main Base Classes for Custom User Models
Django gives two common ways to build a custom user model:
AbstractUser
This is the easiest and most beginner-friendly option.
It behaves like Django’s default user model, but lets you add or modify fields.
AbstractBaseUser
This is more advanced.
It gives you full control over authentication behavior, but you must implement more yourself, including manager logic and required fields.
A useful rule is:
- use
AbstractUserwhen you want to customize the user while keeping Django’s familiar structure - use
AbstractBaseUserwhen you want to redesign authentication more deeply
For most projects, AbstractUser is the best starting point.
8. Best Beginner-Friendly Choice: AbstractUser
Let us begin with the recommended approach for many projects: subclassing AbstractUser.
This gives you:
- username/password/auth support
- admin compatibility
- permissions integration
- easy field extension
And you can add your own fields cleanly.
Example goals:
- keep username
- add phone number
- add profile image
- add bio
This is the easiest path to a custom user model.
9. Create the Custom User Model with AbstractUser
Create or choose an app, for example accounts.
accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
phone = models.CharField(max_length=20, blank=True, null=True)
bio = models.TextField(blank=True, null=True)
profile_picture = models.ImageField(upload_to='profile_pics/', blank=True, null=True)
def __str__(self):
return self.usernameThis model extends the normal Django user and adds three extra fields.
10. Deep Explanation of This Model
By inheriting from AbstractUser, your model already gets all the normal user fields such as username, email, password, is_staff, and permissions-related fields.
That means you do not need to redefine them manually.
Then you add your own fields like:
phonebioprofile_picture
This is why AbstractUser is so convenient: it keeps Django’s robust authentication behavior while giving you customization space.
The model remains fully integrated with Django auth, but now it is yours.
11. Tell Django to Use Your Custom User Model
This step is critical.
In settings.py, add:
AUTH_USER_MODEL = 'accounts.CustomUser'This tells Django that from now on, the official user model of the project is not the default auth.User, but your own model.
This line must be added before your first migrations in a new project.
That timing matters a lot.
12. Why AUTH_USER_MODEL Must Be Set Early
This is one of the most important warnings in all of Django authentication.
If you start your project using the default user model, run migrations, build relationships, and only later decide to replace it with a custom model, the migration process becomes difficult and sometimes messy.
Why?
Because many tables and foreign keys may already depend on the original auth_user table.
Changing the foundational user model after the project grows means reworking those dependencies carefully.
That is why experienced Django developers strongly recommend deciding on the user model at the beginning.
13. Run Migrations
After defining the model and setting AUTH_USER_MODEL, run:
python manage.py makemigrations
python manage.py migrateThis creates the custom user table according to your model.
If this is a new project, the process should be smooth.
If it is not a new project and you already migrated with the default user model, stop and think carefully before continuing. This is one of those areas where starting clean is often easier than patching later.
14. Create a Superuser
Now create an admin user:
python manage.py createsuperuserIf your model still uses username as the main login field, Django will ask for:
- username
- password
Then you can log into the admin site.
15. Register the Custom User in Admin
If you want your custom fields to appear nicely in Django admin, register the model.
accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
@admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
fieldsets = UserAdmin.fieldsets + (
('Additional Info', {
'fields': ('phone', 'bio', 'profile_picture'),
}),
)16. Why Use UserAdmin Here
UserAdmin is Django’s built-in admin configuration for user models. By subclassing it, you keep all the default admin behavior for authentication, permissions, groups, password hashing, and staff settings.
Then you simply extend it with your additional fields.
This is much better than registering the model in a completely plain way, because you preserve Django’s rich user-management interface.
17. Referencing the User Model Correctly
Another very important rule: once you use a custom user model, do not hardcode User from django.contrib.auth.models all over your project.
Instead, use Django’s flexible mechanisms.
In Python code
from django.contrib.auth import get_user_model
User = get_user_model()In model relationships
from django.conf import settings
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)18. Why This Rule Matters
If you import Django’s default User directly, your code becomes tightly coupled to the default model, which defeats the purpose of customization.
Using get_user_model() and settings.AUTH_USER_MODEL ensures your code always points to the active user model of the project, whether default or custom.
This is one of the habits that separates a quick tutorial project from a professional Django codebase.
19. Creating Forms for the Custom User Model
If you add custom fields, you often need custom forms too.
For example, a user creation form in admin or registration may need to expose your extra fields.
Example registration form
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ('username', 'email', 'phone', 'bio')This uses Django’s standard password validation behavior but adapts the form to your custom model.
20. Why Built-In Auth Forms Sometimes Need Adjustment
Django’s built-in auth forms often assume a certain user structure. When you use a custom user model, especially if you add or remove fields, some forms may need to be updated so they match your model.
For example:
- registration form
- user change form
- admin user creation form
- profile update form
This is normal. Once you customize the model, the surrounding forms often need to reflect that customization.
21. Using Email as the Login Field
Now let us move to a more advanced and very common goal: using email instead of username as the main login identifier.
This is where many projects start needing more than a simple extension.
You may want:
- no username field at all
- unique email per user
- authentication based on email
- forms and admin adapted accordingly
For this, AbstractBaseUser is often the cleanest approach.
22. Custom User Model with AbstractBaseUser
This approach gives full control but requires more setup.
accounts/models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('The Email field must be set')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self.create_user(email, password, **extra_fields)
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
full_name = models.CharField(max_length=150, blank=True)
phone = models.CharField(max_length=20, blank=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
date_joined = models.DateTimeField(auto_now_add=True)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
def __str__(self):
return self.email23. Deep Explanation of AbstractBaseUser
This version is much more manual than AbstractUser.
AbstractBaseUser gives you the basic password and login behavior, but not the full ready-made structure of Django’s normal User.
That means you must define more things yourself:
- your fields
- your manager
USERNAME_FIELD- superuser creation logic
- admin integration
- some auth expectations like
is_staff
This is why it is more powerful but also more advanced.
Use it when you truly want custom authentication identity, such as email-based login without username.
24. Why We Need a Custom Manager
The manager is not just extra code. It is essential.
Django expects user models to know how to create normal users and superusers properly. That is why we define:
create_usercreate_superuser
Inside create_user, we normalize the email, create the model instance, hash the password with set_password, and save it.
Inside create_superuser, we enforce the required admin flags like is_staff=True and is_superuser=True.
Without this manager, Django would not know how to create users correctly for your custom auth model.
25. The Meaning of USERNAME_FIELD
This line is one of the most important:
USERNAME_FIELD = 'email'It tells Django which field uniquely identifies the user for authentication.
If you use email login, then email becomes the main identity field.
This affects:
- authentication logic
- createsuperuser prompts
- admin behavior
- auth forms expectations
It is a central setting for the model.
26. The Meaning of REQUIRED_FIELDS
This line tells Django which additional fields should be requested when creating a superuser with the command line, beyond the USERNAME_FIELD and password.
Example:
REQUIRED_FIELDS = ['full_name']If you leave it empty, Django only asks for email and password.
This is useful for controlling the minimal required identity data of your system.
27. Why PermissionsMixin Is Included
When using AbstractBaseUser, you usually also include PermissionsMixin.
Why?
Because it adds important permission-related fields and behavior such as:
is_superuser- groups
- user permissions
Without it, your custom user model would not integrate properly with Django’s permission framework.
So AbstractBaseUser + PermissionsMixin is the common combination for a fully custom auth model.
28. Admin Setup for AbstractBaseUser Models
If you use AbstractBaseUser, admin registration needs more configuration because Django cannot assume the same form structure as the default user model.
A basic example:
accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
@admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
model = CustomUser
list_display = ('email', 'full_name', 'is_staff', 'is_active')
ordering = ('email',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal Info', {'fields': ('full_name', 'phone')}),
('Permissions', {'fields': ('is_staff', 'is_active', 'is_superuser', 'groups', 'user_permissions')}),
('Important Dates', {'fields': ('last_login',)}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'full_name', 'phone', 'password1', 'password2', 'is_staff', 'is_active')}
),
)
search_fields = ('email', 'full_name')This keeps Django admin usable with your email-based user model.
29. Authentication with a Custom User Model
If you use AbstractUser, Django authentication usually works almost the same as before.
If you use AbstractBaseUser with email as USERNAME_FIELD, authentication will use email as the identifier. Your login forms should reflect that.
For example, in a login template, instead of labeling the field “Username”, you may want to label it “Email”.
This is not only a technical detail. It is also part of making the interface consistent with the actual authentication model.
30. Common Beginner Mistakes
Mistake 1: Creating the custom user model too late
This is the biggest mistake. Changing user models after many migrations is painful.
Mistake 2: Importing Django’s default User directly
This breaks flexibility. Use get_user_model() or settings.AUTH_USER_MODEL.
Mistake 3: Using AbstractBaseUser when AbstractUser would be enough
Sometimes beginners choose the hardest option unnecessarily.
Mistake 4: Forgetting the custom manager with AbstractBaseUser
Without it, user creation and superuser creation will not work properly.
Mistake 5: Forgetting admin integration
Then managing users through admin becomes difficult.
Mistake 6: Forgetting AUTH_USER_MODEL in settings
Then Django still uses the default user model.
31. Which Option Should You Choose?
Here is a practical recommendation.
Choose AbstractUser if:
- you want an easier start
- you mostly want extra fields
- you are fine keeping Django’s core auth structure
- you want fewer complications
Choose AbstractBaseUser if:
- you want email login with no username
- you want deep control over authentication identity
- you are ready for more manual setup
- the auth structure is central to your business logic
For many projects, AbstractUser is the sweet spot.
32. Real-World Architectural Advice
In real development, a very good default decision is:
- create a custom user model at the beginning
- base it on
AbstractUser - keep it simple at first
- extend it later if needed
Why is this such a strong strategy?
Because it gives you future flexibility without forcing you into the complexity of AbstractBaseUser too early.
You can still add profile models later for optional information, but your main auth model is already under your control.
This is a balanced and professional approach.
33. Example of Referencing the User in Other Models
Suppose you have a blog post model.
from django.db import models
from django.conf import settings
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
content = models.TextField()
def __str__(self):
return self.titleThis is the correct way.
Notice that we do not write:
from django.contrib.auth.models import UserThat small detail matters a lot for long-term project stability.
34. Testing the Custom User Model
A custom user model should be tested just like any critical part of the project.
Example:
from django.test import TestCase
from django.contrib.auth import get_user_model
User = get_user_model()
class CustomUserModelTest(TestCase):
def test_create_user(self):
user = User.objects.create_user(
username='alice',
email='alice@example.com',
password='testpass123'
)
self.assertEqual(user.email, 'alice@example.com')
self.assertTrue(user.check_password('testpass123'))
If you use an email-only model:
class EmailUserModelTest(TestCase):
def test_create_user(self):
user = User.objects.create_user(
email='alice@example.com',
password='testpass123'
)
self.assertEqual(user.email, 'alice@example.com')
self.assertTrue(user.check_password('testpass123'))This helps verify that your manager and authentication logic work correctly.
35. Full Simple Example with AbstractUser
Here is the clean version many beginners should start with.
models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
phone = models.CharField(max_length=20, blank=True, null=True)
bio = models.TextField(blank=True, null=True)
def __str__(self):
return self.usernamesettings.py
AUTH_USER_MODEL = 'accounts.CustomUser'admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
@admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
fieldsets = UserAdmin.fieldsets + (
('Additional Info', {'fields': ('phone', 'bio')}),
)Referencing in another model
from django.db import models
from django.conf import settings
class Article(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
This is a solid, practical foundation.
36. What You Learned in This Tutorial
In this tutorial, you learned that Django’s user system is one of the most important architectural choices in a project. You saw the difference between extending the default user with a profile model and building a full custom user model. You learned why custom user models are best decided at the beginning of a project, and why late changes are difficult. You explored the two main approaches: AbstractUser for simple and flexible extension, and AbstractBaseUser for full authentication control. You also learned how to configure AUTH_USER_MODEL, register the custom model in admin, create forms, define a manager, use USERNAME_FIELD, and correctly reference the user model throughout the project.
More importantly, you now understand the design reasoning behind these choices, not just the syntax.
37. Conclusion
The custom user model is not just an advanced topic. It is a strategic decision that affects almost every serious Django project. The earlier you think about it, the better. For many developers, the best practical solution is to start with a custom model based on AbstractUser, because it preserves Django’s simplicity while keeping future flexibility. For projects that need deeper control, such as email-based authentication without usernames, AbstractBaseUser becomes the right tool.
The most important lesson is this: choose the level of customization that matches your project’s real needs, but do not postpone the decision until the project becomes large. In Django, user model design is one of those choices where early clarity saves enormous effort later.
💬 Comments
No comments yet. Be the first to comment!
Login to comment.