Configuration¶
All configuration is provided through the RLS_TENANTS dictionary in your Django
settings module.
Settings Reference¶
RLS_TENANTS = {
"TENANT_MODEL": "myapp.Tenant",
"DATABASES": ["default"],
"STRICT_MODE": False,
"TENANT_FK_FIELD": "tenant",
"GUC_PREFIX": "rls",
"USER_PARAM_NAME": "as_user",
"TENANT_PK_TYPE": "int",
"USE_LOCAL_SET": False,
}
TENANT_MODEL¶
| Type | str |
| Default | None (required) |
| Example | "myapp.Tenant" |
Dotted path to your tenant model, in the same format as Django's AUTH_USER_MODEL.
This model is used as the target for the auto-generated ForeignKey on RLSProtectedModel
subclasses.
DATABASES¶
| Type | list[str] |
| Default | ["default"] |
Database aliases to set GUC variables on. In multi-database setups (e.g., read replicas), add all aliases that serve RLS-protected queries.
The middleware sets GUCs on all configured aliases during each request. A
connection_created signal handler also sets GUCs on lazily-created connections
that don't exist when the middleware runs.
Warning
Each alias must exist in Django's DATABASES setting. A typo (e.g., "replca")
will trigger system check W006 at startup and cause runtime errors.
STRICT_MODE¶
| Type | bool |
| Default | False |
When True, TenantQuerySet evaluation methods raise NoTenantContextError
if no RLS context is active. This catches accidental queries outside a
tenant_context(), admin_context(), for_user(), or RLSTenantMiddleware
scope at the point of query execution rather than silently returning zero rows.
Guarded methods: _fetch_all() (iteration), count(), exists(),
aggregate(), update(), delete(), iterator(), bulk_create(),
bulk_update(), get(), first(), last().
RLS_TENANTS = {
"TENANT_MODEL": "myapp.Tenant",
"STRICT_MODE": True, # Recommended for development
}
Recommended usage: Enable in development and staging to surface missing context early. In production, the fail-closed behavior (zero rows) is still the safety net, but strict mode makes the failure loud instead of silent.
import os
RLS_TENANTS = {
"TENANT_MODEL": "myapp.Tenant",
"STRICT_MODE": os.environ.get("DJANGO_ENV") != "production",
}
Note
Strict mode does not change the database-level behavior. RLS policies continue to enforce fail-closed isolation regardless of this setting. Strict mode adds an application-level check that raises before the query reaches the database.
TENANT_FK_FIELD¶
| Type | str |
| Default | "tenant" |
The name of the ForeignKey field added to RLSProtectedModel subclasses. The database
column will be {field_name}_id (e.g., tenant_id).
RLS_TENANTS = {
"TENANT_MODEL": "myapp.Tenant",
"TENANT_FK_FIELD": "organization", # field: organization, column: organization_id
}
GUC_PREFIX¶
| Type | str |
| Default | "rls" |
Prefix for PostgreSQL GUC (Grand Unified Configuration) variable names. The library derives two GUC variables from this prefix:
{prefix}.current_tenant-- holds the current tenant ID{prefix}.is_admin--"true"for admin bypass
# Default: rls.current_tenant, rls.is_admin
RLS_TENANTS = {
"GUC_PREFIX": "rls",
}
# Custom: myapp.current_tenant, myapp.is_admin
RLS_TENANTS = {
"GUC_PREFIX": "myapp",
}
Warning
If you change GUC_PREFIX after running migrations, the RLS policies in the database
will still reference the old GUC names. You will need to recreate the migrations
or manually update the policies.
USER_PARAM_NAME¶
| Type | str |
| Default | "as_user" |
The parameter name that the @with_rls_context decorator looks for in decorated
function signatures. When the decorator finds this parameter, it extracts the user
object and sets the appropriate RLS context.
@with_rls_context
def process_order(request, current_user): # matches USER_PARAM_NAME
orders = Order.objects.all() # automatically scoped
See Context Managers for details on @with_rls_context.
TENANT_PK_TYPE¶
| Type | str |
| Default | "int" |
| Allowed values | "int", "bigint", "uuid" |
The SQL type used to cast the tenant ID in the RLS policy. Must match the primary key type of your tenant model.
# For UUID primary keys:
RLS_TENANTS = {
"TENANT_MODEL": "myapp.Tenant",
"TENANT_PK_TYPE": "uuid",
}
USE_LOCAL_SET¶
| Type | bool |
| Default | False |
When True, uses SET LOCAL (transaction-scoped) instead of set_config()
(session-scoped) to set GUC variables.
When to enable:
- You are using a connection pooler (PgBouncer, pgpool) in transaction mode.
SET LOCALensures GUCs are automatically cleared at transaction end, preventing cross-request leaks through pooled connections.
Requirements when enabled:
- Django's
ATOMIC_REQUESTSshould beTrue, or you must wrap all database operations intransaction.atomic()blocks.
DATABASES = {
"default": {
# ...
"ATOMIC_REQUESTS": True,
}
}
RLS_TENANTS = {
"TENANT_MODEL": "myapp.Tenant",
"USE_LOCAL_SET": True,
}
See Connection Pooling for detailed setup instructions.
System Checks¶
django-rls-tenants registers Django system checks that warn about common misconfigurations:
| Check | Severity | Description |
|---|---|---|
W001 |
Warning | RLSConstraint.guc_tenant_var doesn't match GUC_PREFIX-derived name |
W002 |
Warning | RLSConstraint.guc_admin_var doesn't match GUC_PREFIX-derived name |
W003 |
Warning | USE_LOCAL_SET=True without ATOMIC_REQUESTS=True on default |
W004 |
Warning | CONN_MAX_AGE > 0 with USE_LOCAL_SET=False (session GUCs may leak) |
W005 |
Warning | Database connection uses a PostgreSQL superuser (RLS bypassed) |
W006 |
Warning | DATABASES contains an alias not defined in settings.DATABASES |
W007 |
Warning | USE_LOCAL_SET=True without ATOMIC_REQUESTS on a configured alias |
Run python manage.py check to see any warnings.
Typo Detection¶
If you add an unrecognized key to RLS_TENANTS, the library emits a UserWarning
listing the known keys. This helps catch typos like TENANT_FK_NAME (should be
TENANT_FK_FIELD).
Environment Variables (Test Suite)¶
The test suite reads database configuration from environment variables:
| Variable | Default |
|---|---|
POSTGRES_DB |
django_rls_tenants_test |
POSTGRES_USER |
postgres |
POSTGRES_PASSWORD |
postgres |
POSTGRES_HOST |
localhost |
POSTGRES_PORT |
5432 |