When you start building a Django application, the default User model often feels limiting. Whether you need to store extra profile information, switch to email‑based authentication, or integrate third‑party services, a custom user model is the clean, future‑proof solution. This guide walks you through every step of creating, configuring, and maintaining a Python Django custom user model, so you can avoid common pitfalls and keep your project scalable.
Why Replace Django’s Default User Model?
Out‑of‑the‑box Django provides a robust auth.User model, but it assumes a specific data structure:
- Username is required and must be unique.
- Only a limited set of fields (first name, last name, email, etc.) are available.
- Changing the model after the first migration is extremely painful.
For most real‑world projects, you’ll eventually need one or more of the following:
- Email‑only login – eliminating usernames entirely.
- Additional profile fields such as phone number, avatar, or subscription status.
- Multi‑tenant support where the same email can exist in different organizations.
- Integration with external identity providers (OAuth, SSO, etc.) that require custom flags.
Implementing a custom user model from day one prevents costly refactors later and aligns your code with Django’s official recommendations.
Step‑by‑Step Guide to Creating a Custom User Model
1. Plan Your Model Structure
Before writing any code, decide which fields are essential. A typical modern user model includes:
email– unique, used for authentication.first_nameandlast_name– optional but handy.is_staffandis_active– for admin control.- Any domain‑specific fields (e.g.,
date_of_birth,profile_picture).
2. Create a New App (Optional but Recommended)
python manage.py startapp accounts
Keeping authentication logic in its own accounts app isolates concerns and makes future reuse easier.
3. Define the Custom User Model
In accounts/models.py, subclass AbstractBaseUser and PermissionsMixin. Use BaseUserManager to handle user creation.
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.auth.models import PermissionsMixin
from django.db import models
from django.utils import timezone
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, **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('email address', unique=True)
first_name = models.CharField('first name', max_length=30, blank=True)
last_name = models.CharField('last name', max_length=30, blank=True)
is_staff = models.BooleanField('staff status', default=False)
is_active = models.BooleanField('active', default=True)
date_joined = models.DateTimeField('date joined', default=timezone.now)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = [] # Email & password are required by default
class Meta:
verbose_name = 'user'
verbose_name_plural = 'users'
def __str__(self):
return self.email
4. Tell Django About the New Model
Add the following line to settings.py before any app that imports django.contrib.auth:
AUTH_USER_MODEL = 'accounts.CustomUser'
This single setting instructs every Django component—admin, authentication backends, and the ORM—to use your custom model.
5. Update the Admin Interface
Without a proper admin configuration, the default admin will break. Create accounts/admin.py:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
from django.utils.translation import gettext_lazy as _
@admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
list_display = ('email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_active', 'is_superuser')
search_fields = ('email', 'first_name', 'last_name')
ordering = ('email',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_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', 'password1', 'password2'),
}),
)
filter_horizontal = ('groups', 'user_permissions')
6. Run Migrations
python manage.py makemigrations accounts
python manage.py migrate
Because the custom model is defined before any migrations that reference auth.User, Django creates the correct database schema without conflicts.
7. Adjust Forms and Authentication Backends (Optional)
If you use Django’s built‑in authentication views, replace the default forms with ones that expect an email field:
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = ('email',)
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = ('email', 'first_name', 'last_name')
Then point your URLs to the custom views or use django-allauth for a ready‑made solution that works seamlessly with a custom user model.
Best Practices & Common Pitfalls
Never Change AUTH_USER_MODEL After Migration
Switching the user model after the first migration requires data migrations, manual SQL, or third‑party tools. Treat the decision as a permanent architectural choice.
Use get_user_model() Everywhere
Hard‑coding CustomUser in imports couples your code to a specific implementation. Instead, import the active model dynamically:
from django.contrib.auth import get_user_model
User = get_user_model()
Keep the Model Lightweight
Store only authentication‑related fields in the custom user model. For richer profile data, create a separate Profile model linked via a OneToOneField. This separation preserves performance and keeps the auth system simple.
Test Authentication Thoroughly
- Verify that
createsuperuserworks with the new fields. - Check password reset, email verification, and login flows.
- Run integration tests that simulate real‑world login attempts.
Consider Third‑Party Packages
If you need advanced features like social login, multi‑factor authentication, or JWT tokens, libraries such as django‑allauth, simplejwt, or django‑extensions already support custom user models. Integrating them early saves time.
Advanced Topics
Using UUIDs as Primary Keys
For added security and easier data migration, replace the default integer id with a UUID:
import uuid
from django.db import models
class CustomUser(AbstractBaseUser, PermissionsMixin):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# ... other fields remain the same
Multi‑Tenant Authentication
When a single database serves multiple customers, include a tenant foreign key and adjust the manager’s get_by_natural_key method to filter by tenant ID.
Custom Permissions
Beyond <
Leave a Reply