← All schemas
Schema Library

Django Auth Database Schema Diagram

Django's built-in auth app creates a set of authentication tables when you run migrate: auth_user, auth_group, auth_permission, plus the many-to-many join tables auth_user_groups, auth_user_user_permissions, and auth_group_permissions. This diagram maps the full schema - including how permissions link to content types through django_content_type, and how admin actions are logged in django_admin_log.

Written by Varun Krishnan · Founder, dbdiagramr

Published · Updated

Source: Django 6.1.2 contrib.auth and admin migrations (django/django) · License: BSD-3-Clause

Tables
9
Core table
auth_user
Join tables
3
Version
6.1.2

Django 6.1.2 built-in auth and admin migrations, checked against the 6.1.x stable branch.

Custom user models, third-party auth apps, and non-database session backends are not shown. django/contrib/auth migrations.

Django database schema diagram showing every table, column, and foreign key

Scroll horizontally to see the full diagram. Click it to open at full size.

Open in DBdiagramr to edit →

Embed this diagram

Paste this HTML into a README, docs page, or blog post. The image links back to the full schema page.

<a href="https://www.dbdiagramr.space/schema/django">
  <img src="https://www.dbdiagramr.space/schemas/django-er-diagram.svg" alt="Django Auth Database Schema Diagram" width="1946" height="982" />
</a>
<p><a href="https://www.dbdiagramr.space/schema/django">Django Auth Database Schema Diagram</a> by dbdiagramr</p>

Tables in the Django schema

ColumnTypeNullableKey
auth_user
idinteger GENERATED BY DEFAULT AS IDENTITYNoPK
passwordvarchar(128)No-
last_logintimestamptzYes-
is_superuserbooleanNo-
usernamevarchar(150)No-
first_namevarchar(150)No-
last_namevarchar(150)No-
emailvarchar(254)No-
is_staffbooleanNo-
is_activebooleanNo-
date_joinedtimestamptzNo-
auth_group
idinteger GENERATED BY DEFAULT AS IDENTITYNoPK
namevarchar(150)No-
auth_permission
idinteger GENERATED BY DEFAULT AS IDENTITYNoPK
namevarchar(255)No-
content_type_idintegerNoFK → django_content_type.id
codenamevarchar(100)No-
auth_user_groups
idinteger GENERATED BY DEFAULT AS IDENTITYNoPK
user_idintegerNoFK → auth_user.id
group_idintegerNoFK → auth_group.id
auth_user_user_permissions
idinteger GENERATED BY DEFAULT AS IDENTITYNoPK
user_idintegerNoFK → auth_user.id
permission_idintegerNoFK → auth_permission.id
auth_group_permissions
idinteger GENERATED BY DEFAULT AS IDENTITYNoPK
group_idintegerNoFK → auth_group.id
permission_idintegerNoFK → auth_permission.id
django_content_type
idinteger GENERATED BY DEFAULT AS IDENTITYNoPK
app_labelvarchar(100)No-
modelvarchar(100)No-
django_admin_log
idinteger GENERATED BY DEFAULT AS IDENTITYNoPK
action_timetimestamptzNo-
object_idtextYes-
object_reprvarchar(200)No-
action_flagsmallintNo-
change_messagetextNo-
content_type_idintegerYesFK → django_content_type.id
user_idintegerNoFK → auth_user.id
django_session
session_keyvarchar(40)NoPK
session_datatextNo-
expire_datetimestamptzNo-

Table relationships

9 relationships connect the tables in this schema. Each entry shows the child columns, the parent columns they reference, and the cardinality.

One-to-many

auth_permission.content_type_id → django_content_type.id

One django content type can have many auth permissions (auth_permission.content_type_id -> django_content_type.id, required).

One-to-many

auth_user_groups.user_id → auth_user.id

One auth user can have many auth user groups (auth_user_groups.user_id -> auth_user.id, required).

One-to-many

auth_user_groups.group_id → auth_group.id

One auth group can have many auth user groups (auth_user_groups.group_id -> auth_group.id, required).

One-to-many

auth_user_user_permissions.user_id → auth_user.id

One auth user can have many auth user user permissions (auth_user_user_permissions.user_id -> auth_user.id, required).

One-to-many

auth_user_user_permissions.permission_id → auth_permission.id

One auth permission can have many auth user user permissions (auth_user_user_permissions.permission_id -> auth_permission.id, required).

One-to-many

auth_group_permissions.group_id → auth_group.id

One auth group can have many auth group permissions (auth_group_permissions.group_id -> auth_group.id, required).

One-to-many

auth_group_permissions.permission_id → auth_permission.id

One auth permission can have many auth group permissions (auth_group_permissions.permission_id -> auth_permission.id, required).

One-to-many

django_admin_log.content_type_id → django_content_type.id

One django content type can have many django admin logs (django_admin_log.content_type_id -> django_content_type.id, optional).

One-to-many

django_admin_log.user_id → auth_user.id

One auth user can have many django admin logs (django_admin_log.user_id -> auth_user.id, required).

Common queries

These PostgreSQL queries run against the schema shown on this page.

Group and direct permissions

List permissions a user gets through groups or directly.

select u.username, g.name as "group", p.codename
from auth_user u
left join auth_user_groups ug on ug.user_id = u.id
left join auth_group g on g.id = ug.group_id
left join auth_group_permissions gp on gp.group_id = g.id
left join auth_permission p on p.id = gp.permission_id
order by u.username, g.name, p.codename;

Recent admin actions

Show recent Django admin log entries with the acting user.

select l.action_time, u.username, l.object_repr, l.action_flag
from django_admin_log l
join auth_user u on u.id = l.user_id
order by l.action_time desc;

Permissions per model

Count permissions attached to each content type.

select ct.app_label, ct.model, count(*) as permissions
from auth_permission p
join django_content_type ct on ct.id = p.content_type_id
group by ct.app_label, ct.model
order by permissions desc, ct.app_label, ct.model;

Users without a group

Find auth users that are not in any group.

select u.id, u.username
from auth_user u
left join auth_user_groups ug on ug.user_id = u.id
where ug.group_id is null;

How Django's permission tables work

Django attaches permissions to models, not tables. Every model gets four rows in auth_permission, one each for add, change, delete, and view, and each row points at the model through django_content_type. That indirection is why adding a model and running migrations silently creates four new permissions.

A user can receive permissions through a group or directly. auth_user_groups plus auth_group_permissions carry role-like membership; auth_user_user_permissions carries one-off grants. The two paths are separate, so a query that checks only groups will miss direct permissions.

The tables arrive in migration order: django_content_type first, then auth_user, auth_group, auth_permission, and the three join tables, then django_admin_log from the admin app.

SELECT DISTINCT p.codename
FROM auth_permission p
JOIN django_content_type ct ON ct.id = p.content_type_id
LEFT JOIN auth_group_permissions gp ON gp.permission_id = p.id
LEFT JOIN auth_user_groups ug ON ug.group_id = gp.group_id AND ug.user_id = 42
LEFT JOIN auth_user_user_permissions up ON up.permission_id = p.id AND up.user_id = 42
WHERE ug.user_id = 42 OR up.user_id = 42;

This lists every permission for user 42 through both groups and direct grants.

Frequently asked questions

Related schema diagrams

Visualize your own database

Paste your PostgreSQL connection string and get an interactive ER diagram of your own schema in under 10 seconds. No signup required.

Try it free →