Introduction
Flask RLS brings PostgreSQL Row-Level Security (RLS) to Flask applications backed by
SQLAlchemy. Security is enforced by the database, not by application-layer query filtering โ
so even raw SQL or a forgotten .filter() cannot leak another tenant's rows.
Flask RLS is the Flask/SQLAlchemy sibling of django-rls, created by Kuldeep Pisda. The concepts and naming mirror django-rls, adapted to SQLAlchemy Core and the Flask request lifecycle.
What is Row-Level Security?โ
Row-Level Security is a PostgreSQL feature that filters rows automatically based on policies you define on a table. When a query runs, PostgreSQL evaluates each policy's predicate and returns only the rows the current session is authorized to see. The application cannot bypass it by "forgetting" a filter โ the rule lives in the database.
Why Flask RLS?โ
- ๐ Database-level security โ enforced by PostgreSQL with
FORCE ROW LEVEL SECURITY. - ๐ข Tenant & user policies โ
TenantPolicy,UserPolicy,CustomPolicy, and the PythonicExpressionPolicy. - โก Pool-safe context โ set per transaction via
set_config('rls.*', โฆ, true), so it cannot leak across pooled connections. - ๐งฉ ORM-agnostic โ binds at the SQLAlchemy engine layer; works with bare SQLAlchemy or Flask-SQLAlchemy.
- ๐งฑ Alembic operations โ manage policies as first-class migration steps.
- ๐งช Proven against PostgreSQL โ isolation and fail-closed behavior are verified against a live database.
How it worksโ
- Define policies with
TenantPolicy/UserPolicy/ExpressionPolicy. - Apply them to your tables (via Alembic operations or generated SQL).
- Initialize the extension and set
g.tenant_id/g.user_idper request.
On every transaction, Flask RLS reads the current context and issues
set_config('rls.tenant_id', โฆ, true) so your policies filter automatically.
from flask_rls import RLS, TenantPolicy
rls = RLS(app, engine=db.engine)
rls.register("invoices", TenantPolicy("tenant_isolation", "tenant_id"))
Ready to try it? Head to the Quick Start.