Contents

Backend Development › Database Operations · also in Data Governance & Privacy

Row-Level Security

The database itself filtering rows per user or tenant.

Also known as: row-level security, RLS, row level security

Row-level security (RLS) lets the database enforce which rows a query may return or change, based on the current user or session. You define a policy (WHERE tenant_id = current_tenant()), and the database automatically appends it to every query against that table. Even a query written without a tenant filter returns only the allowed rows.

CREATE POLICY tenant_isolation ON orders
  USING (tenant_id = current_setting('app.tenant_id')::int);

Where column privileges say which operations on which tables are allowed, RLS says which rows. It’s a strong defence in depth: even if application code forgets a WHERE tenant_id = ?, the database applies it.

It’s used most in multi-tenancy and for per-user access rules, where the alternative is trusting every query in the codebase to include the right filter.

The classic mistakes:

  • Treating it as a replacement for application checks. RLS is defence in depth, not the only line. Application logic should still scope queries correctly; RLS catches the inevitable miss.
  • Forgetting to set the session context. A policy based on current_setting('app.tenant_id') needs the application to set that value per request/connection. If it’s unset, the policy may allow or deny unexpectedly. Set it reliably (and reset on connection reuse).
  • Ignoring pooled connections. With connection pooling, a session setting can leak between requests if not reset. Set the tenant per transaction/request and clear it.
  • Not covering all operations. Policies can differ for SELECT, INSERT, UPDATE, DELETE; a policy that only filters reads may leave writes unscoped. Define policies for each relevant command.
  • Performance. The policy predicate is applied to every query; index the columns it uses (often the tenant id) so it doesn’t slow things down.
  • Assuming it applies to table owners. Owners and superusers often bypass RLS by default — so a connection using a powerful role sees everything. Use a restricted role for the application.
  • Overlooking complex policies. Multiple overlapping policies (permissive vs restrictive) combine in ways that surprise; test what each role actually sees.

How to use it: define policies per table for the tenant/user dimension, set session context per request on a restricted role, index the policy columns, and treat it as a safety net behind correct application scoping. It’s the database enforcing isolation that multi-tenancy demands, and a good example of least privilege applied to rows.