django-rls-tenants¶
Database-enforced multitenancy for Django using PostgreSQL Row-Level Security.
Why Row-Level Security?¶
Most Django multitenancy libraries filter data in application code -- through ORM managers, middleware, or custom querysets. If a developer forgets a filter, or writes raw SQL, tenant data leaks silently.
django-rls-tenants pushes isolation into the database using PostgreSQL
Row-Level Security policies.
Every query -- ORM, raw SQL, dbshell, migration scripts -- is subject to the same
policy. The database itself becomes the trust boundary.
Key Features¶
- Database-enforced isolation -- RLS policies apply to every query, not just ORM calls.
- Fail-closed by default -- missing tenant context returns zero rows, never leaks data.
- Single schema, single database -- no schema-per-tenant overhead.
- API-agnostic -- works with Django REST Framework, GraphQL, async views, management commands.
- Clean internal layering -- generic
rls/primitives separate fromtenants/conveniences. - Configurable escape hatches -- bypass flags for authentication, admin, migrations.
- Drop-in for new projects -- abstract model, middleware, and test helpers included.
How It Works¶
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Request │───▶│ Middleware │───▶│ Set GUCs │
│ │ │ resolves │ │ on PG conn │
│ │ │ tenant user │ │ │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ PostgreSQL │
│ RLS Policy │
│ filters rows│
└──────────────┘
- Middleware reads
request.userand extracts tenant identity via theTenantUserprotocol. - GUC variables (
rls.current_tenant,rls.is_admin) are set on the PostgreSQL connection. - RLS policies (created automatically via Django migrations) filter every query at the database level.
- Fail-closed: if no GUC is set, the policy returns zero rows -- no data leak is possible.
Comparison with Alternatives¶
| Feature | django-rls-tenants | django-tenants | django-multitenant |
|---|---|---|---|
| Isolation mechanism | RLS policies | Separate schemas | ORM rewriting |
| Raw SQL protected | Yes | Yes (schemas) | No |
| Single schema | Yes | No | Yes |
| No connection routing | Yes | No | Depends |
| Fail-closed on missing ctx | Yes | N/A | No |
| Works with any API layer | Yes | Yes | Yes |
Requirements¶
| Dependency | Version |
|---|---|
| Python | >= 3.11 |
| Django | >= 4.2 |
| PostgreSQL | >= 15 |
Quick Start¶
# settings.py
INSTALLED_APPS = [
# ...
"django_rls_tenants",
]
RLS_TENANTS = {
"TENANT_MODEL": "myapp.Tenant",
}
MIDDLEWARE = [
# ...
"django_rls_tenants.RLSTenantMiddleware",
]
See the full Quick Start tutorial for a complete walkthrough.
Try the demo
See django-rls-tenants in action with the
example project.
cd example && docker compose up, and explore tenant isolation in under 5 minutes.