Everyone is curious about how large companies manage their SaaS-based software. In this blog post, I will guide you through how to use the django-tenants library to implement multi-tenancy in Django.
Multi-tenancy is a software architecture where a single application instance serves multiple customers (tenants), with each tenant’s data securely isolated from others. Django-tenants is a powerful and widely used library that makes implementing multi-tenancy in Django simple and scalable.
In this complete guide, you’ll learn everything you need to get started with Django Tenants—from basic concepts to practical implementation.
What is Multi-Tenancy?
Multi-tenancy allows you to run multiple organizations or clients on a single application deployment. Each tenant has its own isolated database schema, ensuring complete data separation while sharing the same codebase and infrastructure.
Common use cases include SaaS applications, HRMS, e-learning platforms, e-commerce marketplaces, and enterprise management systems, where each client needs their own isolated environment.
Real-World Companies Using Multi-Tenancy
Many leading companies rely on multi-tenant architecture, including Salesforce (CRM), Shopify (e-commerce), and Slack (team communication).
Internally, what they use the company didn’t do to reveal, but django Tenancy provides the same architecture
Why Django-Tenants?
Django-tenants provides schema-based multi-tenancy using PostgreSQL schemas. Each tenant gets their own database schema, providing strong data isolation while being more efficient than separate databases. The library handles tenant identification, routing, and database operations automatically.
Prerequisites
Before starting, ensure you have Python 3.8 or higher, PostgreSQL 10 or higher installed, and basic knowledge of Django. Django-tenants works best with PostgreSQL due to its schema support.
Installation
First, install the required packages:
pip install django-tenants psycopg2-binary
Create a new Django project if you haven’t already:
django-admin startproject myproject
cd myproject
python manage.py startapp tenants
Configuration
Update your settings.py file with the following configurations. Start by modifying the database settings to use PostgreSQL:
DATABASES = {
'default': {
'ENGINE': 'django_tenants.postgresql_backend',
'NAME': 'tenant_db',
'USER': 'postgres',
'PASSWORD': 'yourpassword',
'HOST': 'localhost',
'PORT': '5432',
}
}
Add django-tenants to your installed apps. The order is crucial here:
INSTALLED_APPS = [
'django_tenants',
'tenants',
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.admin',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
Configure the tenant model and middleware:
TENANT_MODEL = "tenants.Client"
TENANT_DOMAIN_MODEL = "tenants.Domain"
MIDDLEWARE = [
'django_tenants.middleware.main.TenantMainMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Specify which apps are shared across all tenants and which are tenant-specific:
SHARED_APPS = [
'django_tenants',
'tenants',
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.admin',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
TENANT_APPS = [
'django.contrib.contenttypes',
'django.contrib.auth',
]
INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS]
Set the public schema name:
PUBLIC_SCHEMA_NAME = 'public'
Creating Tenant Models
Create your tenant and domain models in tenants/models.py:
from django_tenants.models import TenantMixin, DomainMixin
from django.db import models
class Client(TenantMixin):
name = models.CharField(max_length=100)
created_on = models.DateField(auto_now_add=True)
auto_create_schema = True
def __str__(self):
return self.name
class Domain(DomainMixin):
pass
The TenantMixin provides essential fields like schema_name and is_active. The auto_create_schema attribute automatically creates the database schema when a new tenant is created.
Running Migrations
Django-tenants requires a special migration process. First, create migrations:
python manage.py makemigrations
Run migrations for the shared apps (public schema):
python manage.py migrate_schemas --shared
This creates the public schema and shared tables. Now you’re ready to create tenants.
Creating Your First Tenant
Create a management command or use the Django shell to create tenants. Here’s an example using the shell:
python manage.py shell
from tenants.models import Client, Domain
# Create the public tenant (required)
public_tenant = Client(
schema_name='public',
name='Public Tenant'
)
public_tenant.save()
public_domain = Domain(
domain='localhost',
tenant=public_tenant,
is_primary=True
)
public_domain.save()
# Create your first actual tenant
tenant = Client(
schema_name='tenant1',
name='Tenant One'
)
tenant.save()
domain = Domain(
domain='tenant1.localhost',
tenant=tenant,
is_primary=True
)
domain.save()
Testing Your Multi-Tenant Setup
Start the development server:
python manage.py runserver
To test different tenants, you’ll need to modify your hosts file or use different domains. For local development, add entries to your hosts file:
tenant1.localhost:8000
tenant2.localhost:8000
Now you can access different tenants at tenant1.localhost:8000 and tenant2.localhost:8000.
Creating Tenant-Specific Views
Create views that automatically work with the current tenant’s data:
from django.shortcuts import render
from django.contrib.auth.models import User
def tenant_dashboard(request):
# This query automatically filters to the current tenant's schema
users = User.objects.all()
context = {
'tenant': request.tenant,
'users': users,
}
return render(request, 'dashboard.html', context)
The request object includes a tenant attribute that gives you access to the current tenant information.
Best Practices
Keep tenant-specific data in TENANT_APPS and shared data like user authentication in SHARED_APPS. Use descriptive schema names that are URL-safe and unique. Always test tenant isolation to ensure data doesn’t leak between tenants.
Implement proper error handling for missing or invalid tenants. Use database connection pooling to handle multiple tenant connections efficiently. Consider implementing tenant creation workflows with proper validation.
Advanced Features
Django-tenants supports custom tenant routing, allowing you to use subdomains, custom domains, or path-based routing. You can implement tenant-specific settings by overriding settings based on the current tenant. The library also supports tenant cloning for quickly setting up new tenants with existing data structures.
Common Pitfalls
Avoid forgetting to run migrate_schemas for both shared and tenant apps. Don’t use absolute imports that bypass tenant middleware. Be careful with static files and media files, ensuring they’re properly scoped per tenant when needed. Always test migrations on a copy of your production database before deploying.
Conclusion
Django-tenants provides a robust solution for building multi-tenant Django applications. By following this guide, you’ve learned how to set up schema-based multi-tenancy, create and manage tenants, and build tenant-aware applications. The library handles the complexity of tenant routing and database isolation, allowing you to focus on building great features for your users. Read More on official docs.


Wow