Django creates 5 core auth tables: auth_user, auth_group, auth_permission, django_content_type, and two join tables (auth_user_groups, auth_group_permissions). The permission system is built on content types each model gets a default add, change, delete, and view permission, and you can create custom ones.
The core tables
auth_user
| Column | Type | What it means |
|---|---|---|
id |
int (PK) | Auto-incrementing primary key. |
password |
varchar(128) | Hashed password (PBKDF2 by default). |
last_login |
datetime | When the user last logged in. Null if never. |
is_superuser |
bool | Bypasses all permission checks. |
username |
varchar(150) | Unique username. |
first_name |
varchar(150) | User's first name. |
last_name |
varchar(150) | User's last name. |
email |
varchar(254) | Email address. Not necessarily unique. |
is_staff |
bool | Can access the Django admin. |
is_active |
bool | Set to False instead of deleting users. |
date_joined |
datetime | Registration time. |
auth_group
| Column | Type | What it means |
|---|---|---|
id |
int (PK) | Auto-incrementing ID. |
name |
varchar(150) | Unique group name (e.g., "Editors", "Moderators"). |
Groups are role containers. Assign permissions to groups, then add users to groups.
auth_permission
| Column | Type | What it means |
|---|---|---|
id |
int (PK) | Auto-incrementing ID. |
name |
varchar(255) | Human-readable name (e.g., "Can add post"). |
content_type_id |
int (FK) | Which model this permission applies to. |
codename |
varchar(100) | Code identifier (e.g., add_post). |
Each model gets 4 default permissions: add_modelname, change_modelname, delete_modelname, view_modelname.
django_content_type
| Column | Type | What it means |
|---|---|---|
id |
int (PK) | Auto-incrementing ID. |
app_label |
varchar(100) | The Django app (e.g., blog). |
model |
varchar(100) | The model name (e.g., post). |
This table maps every model in your project to an ID. The permission system uses it to know which model a permission applies to.
auth_user_groups
Join table for many-to-many: users ↔ groups.
| Column | Type | What it means |
|---|---|---|
id |
int (PK) | Auto-incrementing ID. |
user_id |
int (FK) | → auth_user.id. |
group_id |
int (FK) | → auth_group.id. |
auth_group_permissions
Join table for many-to-many: groups ↔ permissions.
| Column | Type | What it means |
|---|---|---|
id |
int (PK) | Auto-incrementing ID. |
group_id |
int (FK) | → auth_group.id. |
permission_id |
int (FK) | → auth_permission.id. |
How permissions work
User ──M──M── Group ──M──M── Permission ──M──1── ContentType
- A permission is tied to a content type (a specific model).
-
Groups collect permissions (e.g., "Editors" get
add_post,change_post). - Users are added to groups to inherit their permissions.
- You can also assign permissions directly to users (bypass groups).
In code:
# Check permission
user.has_perm('blog.add_post')
# Add user to group
from django.contrib.auth.models import Group
editors = Group.objects.get(name='Editors')
user.groups.add(editors)
# Create custom permission
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from blog.models import Post
content_type = ContentType.objects.get_for_model(Post)
custom_perm = Permission.objects.create(
codename='publish_post',
name='Can publish post',
content_type=content_type,
)
What to change
-
Add a
rolefield toauth_userif you need simple role-based access (or use groups). -
Add an
avatarfield for profile pictures. -
Create custom permissions for fine-grained access control (e.g.,
publish_post,archive_post). -
Add
unique=Truetoemailif you want unique emails.
What to leave alone
- Don't modify
django_content_typeit's managed by Django's migration framework. - Don't change
auth_permission.codenameit's referenced byhas_perm()and decorators. - Don't delete
auth_user.is_activeuse it instead of deleting users (preserves foreign keys).
FAQ
Does Django create all these tables automatically?
Yes. Running python manage.py migrate creates all auth tables. They're part of Django's built-in django.contrib.auth app.
Can I use Django without the permission system?
Yes. Remove django.contrib.auth from INSTALLED_APPS and you lose the permission tables but keep the auth_user model (or replace it entirely).
What's the difference between is_superuser and is_staff?
is_superuser bypasses all permission checks. is_staff only controls access to the Django admin. A superuser automatically has is_staff=True.
How do I see my Django auth tables?
Use dbdiagramr paste your connection string and get a visual schema of your Django auth tables.

Top comments (0)