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.User

It already includes useful fields like:

  • username
  • first_name
  • last_name
  • email
  • password
  • is_staff
  • is_superuser
  • is_active
  • last_login
  • date_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 User stores auth-related fields
  • Profile stores 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 AbstractUser when you want to customize the user while keeping Django’s familiar structure
  • use AbstractBaseUser when 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.username

This 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:

  • phone
  • bio
  • profile_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 migrate

This 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 createsuperuser

If your model still uses username as the main login field, Django will ask for:

  • username
  • email
  • 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.email

23. 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_user
  • create_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.title

This is the correct way.

Notice that we do not write:

from django.contrib.auth.models import User

That 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.username

settings.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.