How to Create a Custom User Model in Django (5.x Guide)

Blog / Django · December 18, 2025 · Updated June 9, 2026 · 8 min read
How to Create a Custom User Model in Django (5.x Guide)

If you are building anything beyond a throwaway prototype, set a custom user model on day one. The single most important rule: define AUTH_USER_MODEL in your settings before you run your first migration. Swapping the user model after your database has been created is painful and often means dropping and rebuilding the schema. The official Django documentation states this plainly: "It's highly recommended to set up a custom user model, even if the default User model is sufficient for you."

The two most common reasons to customise the user model are: (1) you want to authenticate with email instead of a username, and (2) you want to add your own fields (display name, phone, organisation, etc.) directly to the user record. This guide covers both, for Django 5.x on Python 3.12+, with complete and idiomatic code you can copy into a fresh app.

When and Why to Use a Custom User Model

Django's built-in authentication is excellent for most projects, but the default User model is hard-coded to use a username field and lives in django.contrib.auth. Once migrations exist that reference it, replacing it is a breaking change.

Set a custom user model when you want to:

  • Log in with email (or phone, or any unique field) instead of username.
  • Add custom fields to the user itself rather than a separate profile model.
  • Keep full control over the authentication record for future requirements.

Even if you don't need any of this today, creating a thin custom model that simply subclasses AbstractUser costs nothing now and saves a migration nightmare later. At MicroPyramid we have shipped Django applications for 12+ years, and starting every new project with a custom user model is one of our non-negotiable conventions.

AbstractUser vs AbstractBaseUser

Django gives you two base classes. Pick based on how much of the default User you want to keep.

AbstractUser AbstractBaseUser + PermissionsMixin
What you get Full default user: username, first_name, last_name, email, is_staff, is_active, password, groups & permissions Only password, last_login, and authentication plumbing
Effort Minimal — subclass and add fields More code — you define every field and a manager
Best for Keep username login, just add fields Replace login field (email-only), drop username entirely
Need a custom manager? No (default works) Yes — BaseUserManager with create_user/create_superuser
Need PermissionsMixin? Already included Yes, to keep Django's permission framework

Rule of thumb: if the default fields are fine and you just want to add a couple, use AbstractUser. If you want email-as-login with no username field at all, use AbstractBaseUser + PermissionsMixin.

Option 1: Extend AbstractUser (the easy path)

This keeps everything Django gives you and lets you bolt on extra fields. Add this to an app dedicated to your user model (commonly an accounts app), in models.py:

# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):
    display_name = models.CharField(max_length=150, blank=True)
    phone = models.CharField(max_length=20, blank=True)

    def __str__(self):
        return self.get_username()

Then tell Django to use it, in settings.py:

# settings.py
AUTH_USER_MODEL = "accounts.User"

Make the migrations and apply them. Because you set AUTH_USER_MODEL before the first migration, this just works:

python manage.py makemigrations accounts
python manage.py migrate

Option 2: AbstractBaseUser for Email-Based Login

When you want to drop username entirely and authenticate with email, subclass AbstractBaseUser and add PermissionsMixin to keep Django's groups and permissions. You also need a custom manager, because Django's default UserManager assumes a username argument.

First, the manager in accounts/managers.py:

# accounts/managers.py
from django.contrib.auth.base_user import BaseUserManager


class UserManager(BaseUserManager):
    """Manager for a user model that uses email as the unique identifier."""

    use_in_migrations = True

    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError("Users must have an email address")
        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)

Now the model in accounts/models.py. Note USERNAME_FIELD = "email", an empty REQUIRED_FIELDS (email is already the username field, so it's never listed there), and that the field used as USERNAME_FIELD must be unique:

# accounts/models.py
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models
from django.utils import timezone

from .managers import UserManager


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField("email address", unique=True)
    display_name = models.CharField(max_length=150, blank=True)

    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    date_joined = models.DateTimeField(default=timezone.now)

    objects = UserManager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []  # email & password are always prompted for

    def __str__(self):
        return self.email

PermissionsMixin supplies is_superuser, the groups and user_permissions relations, and the has_perm() / has_module_perms() methods, so you don't have to write the permission machinery yourself.

Wire it up and migrate exactly as before:

# settings.py
AUTH_USER_MODEL = "accounts.User"

Referencing the User Model in Your Code

Never import the user model directly with from accounts.models import User in other apps or in foreign keys — that hard-codes a dependency and breaks reusability. Use settings.AUTH_USER_MODEL (a string) for model definitions, and get_user_model() for runtime code (views, signals, tests). For more on related patterns, see our post on custom managers in Django.

from django.conf import settings
from django.db import models


class Book(models.Model):
    # Use the string setting in ForeignKey / OneToOne / ManyToMany definitions.
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )


# In views, signals, management commands, tests -- use get_user_model():
from django.contrib.auth import get_user_model

User = get_user_model()
active_users = User.objects.filter(is_active=True)

Admin Registration with a Custom UserAdmin

If you used AbstractUser, you can often just register with the built-in UserAdmin. For an email-based AbstractBaseUser, you need custom forms and an admin that knows there is no username field. First, the forms in accounts/forms.py:

# accounts/forms.py
from django.contrib.auth.forms import UserChangeForm, UserCreationForm

from .models import User


class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = User
        fields = ("email", "display_name")


class CustomUserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = ("email", "display_name", "is_active", "is_staff")

Django's modern UserCreationForm and UserChangeForm already handle password hashing and the read-only password hash display for you, so subclassing them is usually all you need. Now register the model in accounts/admin.py:

# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin

from .forms import CustomUserChangeForm, CustomUserCreationForm
from .models import User


@admin.register(User)
class UserAdmin(BaseUserAdmin):
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm
    model = User

    list_display = ("email", "display_name", "is_staff", "is_active")
    list_filter = ("is_staff", "is_active")
    search_fields = ("email", "display_name")
    ordering = ("email",)

    # No "username" field exists, so redefine the admin fieldsets.
    fieldsets = (
        (None, {"fields": ("email", "password")}),
        ("Personal info", {"fields": ("display_name",)}),
        ("Permissions", {
            "fields": ("is_active", "is_staff", "is_superuser", "groups", "user_permissions"),
        }),
        ("Important dates", {"fields": ("last_login", "date_joined")}),
    )
    add_fieldsets = (
        (None, {
            "classes": ("wide",),
            "fields": ("email", "display_name", "password1", "password2"),
        }),
    )

The Migration Gotcha (and How to Recover)

The most common mistake is running migrations against the default User before switching to a custom model. Once django.contrib.auth's initial migration has created the auth_user table and other tables hold foreign keys to it, Django won't let you change AUTH_USER_MODEL cleanly — you'll hit errors like "Cannot change AUTH_USER_MODEL" or inconsistent migration history.

If you are still early (development, no real data): the simplest fix is to drop the database (or delete the SQLite file), delete your apps' migration files (keep __init__.py), set AUTH_USER_MODEL, then makemigrations and migrate from a clean slate.

If you already have production data: swapping the user model is a non-trivial data migration. It is far cheaper to start with a custom model. If you must migrate later, plan a careful multi-step data migration and test it on a copy of production first. For the mechanics of writing such migrations, see our guides on writing custom migrations in Django and creating initial migrations for an existing schema.

Create a Superuser and Verify

With the custom model in place, createsuperuser will prompt for your USERNAME_FIELD (email) instead of a username:

python manage.py createsuperuser
# Email address: admin@example.com
# Password: ********

Frequently Asked Questions

When should I create a custom user model in Django?

Create it at the very start of every new project, before the first migration. Even if the default User works today, setting up a thin custom model that subclasses AbstractUser is free now and avoids an expensive migration if requirements change. Django's own docs strongly recommend it.

What is the difference between AbstractUser and AbstractBaseUser?

AbstractUser gives you Django's full default user (username, names, email, permissions) so you only add extra fields. AbstractBaseUser is a bare base with just password and authentication plumbing — you define every field yourself and write a manager. Use AbstractUser to extend, and AbstractBaseUser + PermissionsMixin to fully replace the login field, for example to log in with email.

How do I make Django log in with email instead of username?

Subclass AbstractBaseUser (plus PermissionsMixin), add a unique email field, set USERNAME_FIELD = "email", leave REQUIRED_FIELDS = [], and provide a custom BaseUserManager whose create_user/create_superuser accept email instead of username. Then set AUTH_USER_MODEL and migrate before any data exists.

Can I change AUTH_USER_MODEL after running migrations?

Not cleanly. Once the default user table and foreign keys to it exist, Django blocks swapping the model. In early development, drop the database and migrations and start fresh. With production data, it requires a careful, tested multi-step data migration — which is exactly why you should set the custom model up first.

Why use get_user_model() and settings.AUTH_USER_MODEL instead of importing User directly?

Directly importing your User class hard-codes a dependency and breaks if the active user model changes. Use the string settings.AUTH_USER_MODEL in model relations (ForeignKey, etc.) and get_user_model() in runtime code so your code always resolves the project's configured user model.

Do I still need a custom manager with AbstractUser?

Usually no. AbstractUser ships with Django's default UserManager, which already provides create_user and create_superuser. You only need a custom BaseUserManager when you switch to AbstractBaseUser or change the login field, since the default manager assumes a username argument.

Wrapping Up

A custom user model is the cheapest insurance you can buy in a Django project: subclass AbstractUser for a quick win, or go to AbstractBaseUser + PermissionsMixin with a custom manager when you want email-based login. The golden rule remains — set AUTH_USER_MODEL before your first migration, and always reference the user via settings.AUTH_USER_MODEL and get_user_model().

If you'd like a team that has done this across dozens of production Django systems, MicroPyramid's Django development services can help with authentication, custom permissions, and the rest of your application. You might also enjoy our post on custom decorators to check user roles and permissions in Django.

Share this article