Skip to main content

django-rls-tenants

CI PyPI version Latest on Django Packages Python versions Django versions Code style: Ruff Type checked: mypy License: MIT

Database-enforced multitenancy for Django using PostgreSQL Row-Level Security. Every query -- ORM, raw SQL, dbshell -- is filtered by the database itself. Missing tenant context returns zero rows, never leaks data.

Why this library?

Existing Django multitenancy libraries enforce isolation at the application level — one missed filter and data leaks across tenants. django-rls-tenants pushes enforcement to PostgreSQL itself using Row-Level Security. Every query — ORM, raw SQL, dbshell — is filtered by the database. Missing tenant context returns zero rows, never leaked data.

Learn more about the RLS approach →

Features

  • Database-enforced isolation -- RLS policies protect tenant tables across ORM queries, raw SQL, dbshell, and data migrations when using a database role subject to RLS (not a superuser or BYPASSRLS role; system check W005 warns about those, and E001 is an error for them when DEBUG is off). It guards against omitted tenant scoping, not arbitrary SQL on the app connection -- see the security model.
  • Fail-closed by default -- missing tenant context returns zero rows; optional STRICT_MODE raises NoTenantContextError on unscoped TenantQuerySet evaluation (STRICT_MODE_EXCLUDE exempts listed models, such as the user model an auth backend reads).
  • Automatic query scoping -- RLSProtectedModel adds the tenant FK, the RLS policy, and a tenant-scoped manager.
  • Index-friendly by design -- the manager's WHERE tenant_id = X is what reaches the planner; the policy alone is a post-scan filter. Composite (tenant, ...) indexes, partial indexes, joins and the v1.3.0 InitPlan form, with measured EXPLAIN plans. Performance guide →
  • Tenant-aware Django admin -- RLSTenantModelAdmin scopes changelists, forms, and related dropdowns to the current tenant, with a tenant switcher for cross-tenant admins. Admin guide →
  • Cross-tenant foreign key validation -- full_clean(), forms, the admin and DRF serializers reject a foreign key to another tenant's row, which RLS cannot catch; validate_cross_tenant_fks() covers service code. Guide →
  • Native Celery integration -- @rls_task carries the active tenant context from enqueue to worker, including across chains, groups, and chords (pip install "django-rls-tenants[celery]"). Celery guide →
  • Django REST Framework integration -- RLSTenantAPIViewMixin opens the RLS context after DRF authenticates the user (token and JWT clients included, which the middleware cannot see), RLSTenantPermission refuses a view that runs outside it, and RLSTenantModelSerializer fills the tenant FK from it (pip install "django-rls-tenants[drf]"). DRF guide →
  • Async context managers -- async with async_tenant_context(...) for async views, consumers and tasks; tenant state is set in the coroutine, so the async ORM is scoped. Async context managers → · Async deployment guide →
  • Per-tenant management commands -- run_as_tenant runs any management command inside one tenant's context and run_for_all_tenants runs it once per tenant (filters, --parallel, --json, a failure summary and exit status), so reports, re-indexing and backfills need no context code of their own. Management commands guide →
  • Audit logging -- structured records on the django_rls_tenants.audit logger each time a tenant or admin context is entered, exited or refused (who, which tenant, which code path), ready for a JSON formatter and routed with Django's LOGGING. Audit logging guide →
  • Users in several tenants -- a user model that adds rls_tenant_ids (MultiTenantUser) selects the tenant a request runs in with an X-Tenant header or switch_tenant(); the middleware, the DRF mixin and the admin check every selection against the user's tenants and refuse any other with a 403. Guide →
  • Raw SQL helpers -- safe_tenant_sql() builds a tenant-filter predicate; current_tenant_value_sql() returns the current tenant ID expression for inserts and projections. Both reuse the expressions used by RLS policies. Raw SQL guide →
  • M2M isolation -- auto-detected subquery-based RLS policies on M2M through tables; check_rls --generate writes the migration that creates them.
  • Third-party tables -- AddRLSPolicy adds the same policy to a table of an app you do not own, and RLS_TENANTS["EXTERNAL_MODELS"] makes check_rls, the cross-tenant foreign key check and the select_related() / prefetch_related() tenant filters treat the model as protected. Guide →
  • Sharing rows across tenants -- extra_predicates (SharedWith, TenantField, RawPredicate) let a tenant read rows another tenant shares with it, in a separate FOR SELECT policy that never lets it update or delete them; including_shared() queries them. Guide →
  • Single schema, single database -- no schema-per-tenant overhead, no connection routing.

Quick Start

pip install django-rls-tenants
# settings.py
INSTALLED_APPS = [..."django_rls_tenants"]

RLS_TENANTS = {
    "TENANT_MODEL": "myapp.Tenant",  # required
}

MIDDLEWARE = [..."django_rls_tenants.RLSTenantMiddleware"]
# models.py
from django_rls_tenants import RLSProtectedModel

class Order(RLSProtectedModel):
    title = models.CharField(max_length=255)
    # tenant FK + RLS policy added automatically
python manage.py migrate
python manage.py check_rls   # verify policies are in place

Full tutorial and documentation → Something not working? Start with the Troubleshooting page (symptom / cause / fix).

Want to see it in action first? Check out the example project -- cd example && docker compose up, then open http://localhost:8000.

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

See the full comparison →

Used in Production

Are you using django-rls-tenants in production? We'd love to hear about it. Used in production? Tell us →

Requirements

Dependency Version
Python 3.12, 3.13, 3.14
Django 5.2, 6.0, 6.1
PostgreSQL 15, 16, 17, 18

See the supported-versions policy for how these lists change.

Contributing

Contributions are welcome! See the contributing guide to get started. Questions and bug reports have their own places: see SUPPORT.md. This project is governed by the Contributor Covenant Code of Conduct.

License

MIT

Metadata

Release files for django-rls-tenants 1.6.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-rls-tenants 1.6.3
File Size Uploaded Provenance
django_rls_tenants-1.6.3.tar.gz 488.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for django-rls-tenants 1.6.3
File Interpreter ABI Platform
django_rls_tenants-1.6.3-py3-none-any.whl Python 3 none any Details

Total release size: 688.7 kB

Release files / django_rls_tenants-1.6.3.tar.gz

Download URL django_rls_tenants-1.6.3.tar.gz
Size 488.7 kB
Tags Source
SHA-256 checksum
How to use checksums
90b79b47088de1ea187a3931edccc449a2e1188e060cf6d7727fedd0724b5df1
BLAKE2b-256 checksum
How to use checksums
cdf864765cc26be4f458ad559f4ee5bc981fb2377d0381045e0ceaf4cf911363
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / django_rls_tenants-1.6.3-py3-none-any.whl

Download URL django_rls_tenants-1.6.3-py3-none-any.whl
Size 200.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b9c89217c4b833ebb269288efbfd0d6682bf7ae1fcbf044b6355e2e2884883eb
BLAKE2b-256 checksum
How to use checksums
d15fdcd5bf0f67d050cb7aebde4d6f240d7c7bbf55d4477f95a6702287d0c89f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for django-rls-tenants 1.6.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_rls_tenants-1.6.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page