Custom User Model
Reviewed & published by Brayan K
A custom user model is a model class you define to replace Django's built-in auth.User, registered through the AUTH_USER_MODEL setting, so you can add fields, log in by email, and control how users are created.
Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll compare AbstractUser with AbstractBaseUser, set AUTH_USER_MODEL the right way, switch login from username to email, and write a custom UserManager that powers create_user and create_superuser.
What You'll Learn in This Lesson
- AbstractUser vs AbstractBaseUser
- Setting AUTH_USER_MODEL in settings.py
- Why you set the user model at project start
- Using email instead of username to log in
- Writing a custom UserManager
- How create_user and create_superuser differ
1 AbstractUser vs AbstractBaseUser
Django gives you two starting points. Subclassing AbstractUser inherits all the standard fields — username, email, password, is_staff, is_active, and the permission relations — so you only add what's new. Subclassing AbstractBaseUser gives you almost nothing but the password machinery, so you define the identifier field, the manager, and the permission mix-in yourself. For most projects, AbstractUser plus a couple of extra fields is the right call.
Here's the simplest possible custom user — just add a field:
# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
bio = models.TextField(blank=True)
is_verified = models.BooleanField(default=False)
def __str__(self):
return self.username# AbstractUser vs AbstractBaseUser, simulated.
# AbstractUser gives you username, email, password, is_staff, etc. for free,
# so you just ADD fields. We model "inheriting" by starting from a base dict.
ABSTRACT_USER_FIELDS = {
"username": "",
"email": "",
"password": "",
"is_staff": False,
"is_active": True,
"is_superuser": False,
}
class User:
# "extends AbstractUser" -> start with all its fields, then add our own
def __init__(self, **kwargs):
fields = dict(ABSTRACT_USER_FIELDS) # inherited fields
fields["bio"] = "" # our NEW custom field
fields["is_verified"] = False # another custom field
fields.update(kwargs)
self.__dict__.update(fields)
def __str__(self):
return self.username
u = User(username="alice", email="alice@example.com", bio="Django learner")
print("username:", u.username)
print("email:", u.email)
print("bio (custom):", u.bio)
print("is_staff (inherited):", u.is_staff)
print("str(user):", str(u))
# EXPECTED:
# username: alice
# email: alice@example.com
# bio (custom): Django learner
# is_staff (inherited): False
# str(user): alice
# ✅ Expected output:
# username: alice
# email: alice@example.com
# bio (custom): Django learner
# is_staff (inherited): False
# str(user): alice2 AUTH_USER_MODEL — Set It Early
Defining the class is not enough — you must tell Django to use it with the AUTH_USER_MODEL setting. This is best done before your first migration, because swapping the user model after foreign keys to it already exist is extremely painful. Always reference settings.AUTH_USER_MODEL in your own foreign keys so your code never hardcodes the user class.
# settings.py
AUTH_USER_MODEL = "accounts.User" # "app_label.ModelName"
# anywhere else you reference the user, do NOT import User directly:
from django.conf import settings
from django.db import models
class Post(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
# in code that needs the class, ask Django for it:
from django.contrib.auth import get_user_model
User = get_user_model()# AUTH_USER_MODEL tells Django which class IS the user. Code should never
# hardcode the user model; it asks the project setting instead. This
# simulates how get_user_model() resolves the right class from a setting.
# Pretend these are two candidate user classes registered in the project
class DefaultUser:
label = "auth.User (Django default)"
class CustomUser:
label = "accounts.User (email login)"
MODEL_REGISTRY = {
"auth.User": DefaultUser,
"accounts.User": CustomUser,
}
# settings.py: AUTH_USER_MODEL = "accounts.User"
AUTH_USER_MODEL = "accounts.User"
def get_user_model():
return MODEL_REGISTRY[AUTH_USER_MODEL]
UserModel = get_user_model()
print("Resolved user model:", UserModel.label)
# This is why you write ForeignKey(settings.AUTH_USER_MODEL),
# not ForeignKey(User) -- the project stays free to swap the user class.
def foreign_key_target(setting):
return f"ForeignKey -> {setting}"
print(foreign_key_target(AUTH_USER_MODEL))
# EXPECTED:
# Resolved user model: accounts.User (email login)
# ForeignKey -> accounts.User
# ✅ Expected output:
# Resolved user model: accounts.User (email login)
# ForeignKey -> accounts.User3 Email Login and a Custom UserManager
To log in by email with no username, you subclass AbstractBaseUser, make email the unique identifier, set USERNAME_FIELD = "email", and attach a custom UserManager. The manager's create_user and create_superuser methods enforce the rules: require an email, normalize it, hash the password, and set the staff/superuser flags. This is exactly what createsuperuser calls on the command line.
# accounts/models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra):
if not email:
raise ValueError("Users must have an email address")
email = self.normalize_email(email)
user = self.model(email=email, **extra)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra):
extra.setdefault("is_staff", True)
extra.setdefault("is_superuser", True)
return self.create_user(email, password, **extra)
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
objects = UserManager()
USERNAME_FIELD = "email" # log in with email
REQUIRED_FIELDS = [] # asked for by createsuperuser
def __str__(self):
return self.email# A custom UserManager controls how users are created. Django calls
# create_user() and create_superuser(). With email-as-username, there's
# no username field, so the manager must require an email instead.
class UserManager:
def __init__(self):
self.users = []
def _create_user(self, email, password, **extra):
if not email:
raise ValueError("Users must have an email address")
email = email.strip().lower() # "normalize" the email
user = {"email": email, "password": f"hashed({password})", **extra}
self.users.append(user)
return user
def create_user(self, email, password=None, **extra):
extra.setdefault("is_staff", False)
extra.setdefault("is_superuser", False)
return self._create_user(email, password, **extra)
def create_superuser(self, email, password=None, **extra):
extra.setdefault("is_staff", True)
extra.setdefault("is_superuser", True)
return self._create_user(email, password, **extra)
objects = UserManager()
regular = objects.create_user("Alice@Example.com ", "pw123")
admin = objects.create_superuser("boss@example.com", "secret")
print("regular email (normalized):", regular["email"])
print("regular is_superuser:", regular["is_superuser"])
print("admin is_staff:", admin["is_staff"])
print("admin is_superuser:", admin["is_superuser"])
try:
objects.create_user("", "nopw")
except ValueError as e:
print("error:", e)
# EXPECTED:
# regular email (normalized): alice@example.com
# regular is_superuser: False
# admin is_staff: True
# admin is_superuser: True
# error: Users must have an email address
# ✅ Expected output:
# regular email (normalized): alice@example.com
# regular is_superuser: False
# admin is_staff: True
# admin is_superuser: True
# error: Users must have an email address🎯 Your Turn: Point the Project at Your Model
The AUTH_USER_MODEL setting uses the form "app_label.ModelName". Replace the blank so the project uses the User model in the accounts app.
# 🎯 YOUR TURN: set the project setting to your custom user model.
# Replace ___ with the string "accounts.User".
AUTH_USER_MODEL = ___
print("Using user model:", AUTH_USER_MODEL)
# EXPECTED OUTPUT:
# Using user model: accounts.UserCommon Errors (And How to Fix Them)
❌ I changed AUTH_USER_MODEL after migrating and everything broke
Django can't swap the user model once tables reference it.
✅ Fix: set AUTH_USER_MODEL before the first migration. If you're stuck, the cleanest path is a fresh database during development.
❌ ForeignKey to User causes circular or stale references
You imported the user class directly instead of the setting.
✅ Fix: use settings.AUTH_USER_MODEL in foreign keys and get_user_model() in code.
❌ createsuperuser fails with "Manager isn't available"
Your model has no manager defining create_superuser, or USERNAME_FIELD is wrong.
✅ Fix: attach a UserManager as objects and set USERNAME_FIELD to your login field.
📋 Quick Reference
| Item | What It Does |
|---|---|
| AbstractUser | Subclass to keep standard fields and add your own |
| AbstractBaseUser | Minimal base for full control (e.g. email-only login) |
| AUTH_USER_MODEL | Setting that tells Django which class is the user |
| USERNAME_FIELD | Which field is used to log in (e.g. "email") |
| get_user_model() | Returns the active user class from settings |
🎯 Mini Challenge: Build an Email-Login Manager
Write a manager with create_user and create_superuser that requires an email, normalizes it, and enforces that a superuser is also staff.
# 🎯 Mini Challenge: a full email-login user + manager in plain Python.
# Build create_user / create_superuser and enforce the rules Django does.
class EmailUserManager:
def __init__(self):
self.db = []
def create_user(self, email, password, is_staff=False, is_superuser=False):
if not email:
raise ValueError("Email is required")
if is_superuser and not is_staff:
raise ValueError("Superuser must have is_staff=True")
user = {
"email": email.strip().lower(),
"password": f"hashed({password})",
"is_staff": is_staff,
"is_superuser": is_superuser,
}
self.db.append(user)
return user
def create_superuser(self, email, password):
return self.create_user(email, password, is_staff=True, is_superuser=True)
objects = EmailUserManager()
objects.create_user("USER@Site.com", "pw")
objects.create_superuser("admin@site.com", "pw")
for u in objects.db:
print(u["email"], "| staff:", u["is_staff"], "| super:", u["is_superuser"])
# Show the guard rails firing:
try:
objects.create_user("", "pw")
except ValueError as e:
print("rejected:", e)
# EXPECTED:
# user@site.com | staff: False | super: False
# admin@site.com | staff: True | super: True
# rejected: Email is required❓ Frequently Asked Questions
Lesson complete — you own the user model!
You can choose between AbstractUser and AbstractBaseUser, set AUTH_USER_MODEL at project start, switch login from username to email, and write a UserManager whose create_user and create_superuser enforce your rules.
Practice quiz
Which setting tells Django to use your custom user class?
- USER_MODEL
- AUTH_USER_MODEL
- DEFAULT_USER
- CUSTOM_USER
Answer: AUTH_USER_MODEL. AUTH_USER_MODEL, written as 'app_label.ModelName', points Django at your custom user model.
What format does AUTH_USER_MODEL use?
- The full import path like accounts.models.User
- Just the model name like User
- 'app_label.ModelName' like 'accounts.User'
- A reference to the class object itself
Answer: 'app_label.ModelName' like 'accounts.User'. AUTH_USER_MODEL uses the 'app_label.ModelName' string, e.g. 'accounts.User'.
When subclassing AbstractUser, what do you get for free?
- Nothing but password and last_login
- All standard fields: username, email, password, is_staff, etc.
- Only an email field
- A REST API for users
Answer: All standard fields: username, email, password, is_staff, etc.. AbstractUser inherits the full set of standard user fields, so you just add extras.
When should you reach for AbstractBaseUser instead of AbstractUser?
- When you want the standard fields plus extras
- When you only need to add one field
- For every project by default
- When you need full control, e.g. email-only login with no username
Answer: When you need full control, e.g. email-only login with no username. AbstractBaseUser is minimal and used when you want full control over the identifier and fields.
Why should you set AUTH_USER_MODEL before the first migration?
- Swapping the user model after foreign keys exist is extremely painful
- Django deletes the default user otherwise
- It speeds up migrations
- Settings can only be changed once
Answer: Swapping the user model after foreign keys exist is extremely painful. Once tables hold foreign keys to the user, Django cannot cleanly swap the user model.
What should you reference in your foreign keys to the user?
- A direct import of the User class
- settings.AUTH_USER_MODEL
- auth.User always
- get_user_model() at module level
Answer: settings.AUTH_USER_MODEL. Use settings.AUTH_USER_MODEL in ForeignKey so the project stays free to swap the user class.
How do you get the active user class in code at runtime?
- from django.contrib.auth.models import User
- settings.AUTH_USER_MODEL()
- get_user_model()
- User.objects.model()
Answer: get_user_model(). get_user_model() returns whatever class AUTH_USER_MODEL points to.
Which attribute names the field used to log in?
- LOGIN_FIELD
- PRIMARY_FIELD
- IDENTIFIER
- USERNAME_FIELD
Answer: USERNAME_FIELD. USERNAME_FIELD names the field (e.g. 'email') used as the login identifier.
Which methods must a custom UserManager provide?
- create_user and create_superuser
- save and delete
- login and logout
- get and filter
Answer: create_user and create_superuser. Django expects create_user and create_superuser on the manager; createsuperuser calls the latter.
Should the USERNAME_FIELD also be listed in REQUIRED_FIELDS?
- Yes, always include it
- No — Django asks for USERNAME_FIELD automatically
- Only for superusers
- Only if it is the email
Answer: No — Django asks for USERNAME_FIELD automatically. REQUIRED_FIELDS holds extra prompts; Django already asks for USERNAME_FIELD, so don't duplicate it.
Continue this course
- Previous: Context Processors
- Next: Formsets & Inline Formsets — Handle many forms at once, including related-object inlines
- Quick reference: Django cheat sheet
Frequently asked questions
What is a custom user model in Django?
A custom user model is a model class you define to replace Django's built-in auth.User. You point the project at it with the AUTH_USER_MODEL setting. This lets you add extra fields like a phone number or verification flag, change how users log in (for example by email instead of username), and control how users are created. Django then uses your class everywhere it would normally use the default user.
What is the difference between AbstractUser and AbstractBaseUser?
AbstractUser gives you all of Django's standard user fields — username, email, password, first_name, last_name, is_staff, is_active, and the permission relations — so you simply subclass it and add fields. AbstractBaseUser is far more minimal: it provides only the password and last_login plumbing, so you must define the identifier field, USERNAME_FIELD, and a manager yourself. Use AbstractUser when you want the standard fields plus extras, and AbstractBaseUser when you want full control, such as logging in purely by email with no username at all.
Why should I set AUTH_USER_MODEL at the very start of a project?
Because changing the user model after you have run migrations is painful. Many tables hold foreign keys to the user, and Django's migration system does not support swapping the user model cleanly once those relations exist. The recommended practice is to create a custom user model — even an empty one that just subclasses AbstractUser — before the first migration, set AUTH_USER_MODEL right away, and reference settings.AUTH_USER_MODEL in your foreign keys. That keeps the door open to add fields later without a migration nightmare.
What does a custom UserManager do?
A UserManager controls how user objects are created. Django expects it to provide create_user and create_superuser methods. When you use email as the login identifier instead of a username, you write a manager that requires and normalizes the email, hashes the password, and sets the right is_staff and is_superuser flags. The manager is attached to the model as the objects attribute, and createsuperuser on the command line calls create_superuser on it.