Sarath KrishnanFeb. 7, 2026
Building a SaaS product almost always leads to one critical architectural decision early on: multi-tenancy.
How do you serve multiple organizations (tenants) from a single codebase while keeping data secure, scalable, and customizable?
In this blog, we’ll design a production-grade multi-tenant architecture using Django (backend) and React (frontend), covering:
This approach works especially well for ERP, accounting, CRM, and finance platforms.
Multi-tenancy means a single application instance serves multiple customers (organizations), while keeping their data logically isolated.
Each company:
Before choosing an approach, let’s briefly compare models:
|
Model |
Description |
Pros |
Cons |
|
Database per tenant |
Separate DB for each org |
Strong isolation |
Hard to scale |
|
Schema per tenant |
One DB, multiple schemas |
Good isolation |
Migration complexity |
|
Row-based (shared DB) |
Tenant ID per row |
Scalable, simple |
Requires discipline |
This blog focuses on Row-Based Multi-Tenancy, which is the most common and scalable approach with Django + React.
At the heart of our architecture is an Organization model.
class Organization(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
The slug will be used for:
Every business-related model must be linked to an organization.
class Invoice(models.Model):
organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name="invoices"
)
invoice_number = models.CharField(max_length=50)
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
Every query must be organization-scoped
❌ Bad:
Invoice.objects.all()
✅ Good:
Invoice.objects.filter(organization=request.organization)
We use the subdomain to identify the tenant.
https://acme.yourapp.com
class OrganizationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
host = request.get_host().split(':')[0]
subdomain = host.split('.')[0]
try:
request.organization = Organization.objects.get(slug=subdomain)
except Organization.DoesNotExist:
request.organization = None
return self.get_response(request)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'yourapp.middleware.OrganizationMiddleware',
...
]
Now every request knows its organization.
class TenantModelViewSet(ModelViewSet):
def get_queryset(self):
return super().get_queryset().filter(
organization=self.request.organization
)
def perform_create(self, serializer):
serializer.save(organization=self.request.organization)
class InvoiceViewSet(TenantModelViewSet):
queryset = Invoice.objects.all()
serializer_class = InvoiceSerializer
This guarantees:
A user can belong to one or multiple organizations.
class OrganizationUser(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
role = models.CharField(max_length=50)
The frontend must also be tenant-aware.
export const getTenant = () => {
const host = window.location.hostname;
return host.split(".")[0];
};
const TenantContext = React.createContext();
export const TenantProvider = ({ children }) => {
const tenant = getTenant();
return (
<TenantContext.Provider value={tenant}>
{children}
</TenantContext.Provider>
);
};
Wrap your app:
<TenantProvider>
<App />
</TenantProvider>
All API calls automatically hit the correct tenant via subdomain.
axios.defaults.baseURL = `${window.location.protocol}//${window.location.host}/api`;
No tenant ID in query params = clean & secure APIs.
const tenantThemes = {
acme: { primary: "#0A3D62" },
globex: { primary: "#1B9C85" },
};
const theme = tenantThemes[tenant] || defaultTheme;
if (tenant === "enterprise") {
showAdvancedReports();
}
This enables:
Key rules:
Fix: Base tenant-aware ViewSets
Fix: Always derive from request/subdomain
Fix: Pass organization explicitly in signals/events
Fix: Use tenant-prefixed cache keys
✅ Accounting / ERP systems
✅ CRM platforms
✅ HR & Payroll SaaS
✅ Inventory & Billing systems
❌ Heavy regulatory isolation needs (use DB-per-tenant instead)
A well-designed multi-tenant architecture with Django & React provides:
By resolving tenants via subdomains, enforcing organization-level filtering, and building tenant-aware frontend logic, you can confidently scale your SaaS without rewriting your core architecture.
0