django-rls-tenants -- database-enforced multitenancy using PostgreSQL RLS

Hi everyone,

I’d like to share a library I’ve been working on: django-rls-tenants.

It moves tenant data isolation from “something your Django code must remember to do” to “something PostgreSQL enforces whether you remember or not,” using Row-Level Security policies.

How I got here

I was building a multi-tenant Django app and evaluating the usual options. Schema-per-tenant felt heavy for what I needed (and the migration-time scaling worried me). Application-level filtering with custom managers was cleaner, but I kept coming back to one question: what happens when someone writes a management command, a Celery task, or a raw SQL query and forgets the tenant filter?

The answer – “all tenant data gets returned” – didn’t sit well with me. So I started looking at PostgreSQL’s Row-Level Security, which has been a core feature since PG 9.5. RLS lets you attach policies to tables that the database enforces on every query – ORM, raw SQL, dbshell, everything. If no tenant context is set, you get zero rows back, not all rows. Fail-closed by default.

What the library does

  • Inherit from RLSProtectedModel – the tenant FK and RLS policy are created automatically during migrate

  • RLSTenantMiddleware sets the PostgreSQL session variable per request (after AuthenticationMiddleware)

  • tenant_context() and admin_context() context managers for Celery tasks, management commands, scripts

  • Defense in depth: for_user() applies both an ORM .filter() and the database-level GUC

  • Test utilities (assert_rls_enabled(), rls_as_tenant(), …) and a check_rls management command for CI

Supports Python 3.11–3.14, Django 4.2–6.0, PostgreSQL 15+. MIT licensed.

Try it

The fastest way to see it working is the included example app – clone the repo, run docker compose up in the example/ directory, and you get a Django app with 3 tenants and sample data. Log in as different users and watch how Note.objects.all() – with no tenant filter in the view – returns only that user’s tenant data. Full docs, quickstart, and the example are all linked from the GitHub repo.

I wrote a longer blog post explaining the approach, the tradeoffs, and when RLS is (and isn’t) the right fit – happy to share if anyone’s interested.

I’d genuinely appreciate feedback on the approach, the API design, or use cases I haven’t considered.

1 Like