Skip to content

What Is Row Level Security? RLS Explained

Row level security filters data inside the database itself. Learn policies, Postgres RLS, multi-tenant patterns and performance costs.

Aydin Monavvari5 min readTechnology
What Is Row Level Security? RLS Explained — branded illustration of layered application windows and code brackets on a deep navy field with emerald and gold accents.

Row level security (RLS) is a database feature that restricts which individual rows a user can read or write, enforced by the database itself on every query. Instead of trusting application code to always remember the correct filter, you define policies once — for example, a user may only touch rows belonging to their own organization — and the database applies them automatically, even to queries nobody anticipated.

This guide explains how row level security works, the policy patterns most teams use, and the performance and correctness trade-offs to weigh honestly.

#Why Row Level Security Matters

In a typical application, data filtering happens in application code: the backend composes a query, appends the right condition, and returns results. That works until it does not. A new developer forgets the tenant filter on one endpoint. A reporting tool connects directly to the database and sees everything. A migration rewrites a query and silently drops the condition. Each mistake is small; the consequence — one customer seeing another customer's data — is not.

Row level security moves that enforcement from everywhere, hopefully, to one place, always. Policies live next to the tables they protect. Every query, from any client, through any code path, is filtered the same way. This is the database equivalent of defense in depth: even if the application layer has a bug, the data layer still holds the line.

#How RLS Works in PostgreSQL

PostgreSQL has the most widely used RLS implementation, and most modern platforms build on it. The mechanics are straightforward:

  1. Enable RLS on a table. Once enabled, the table is protected by default, and queries run through the policy filters.
  2. Write policies. Each policy is a named rule attached to the table, specifying whom it applies to and what it allows. A read policy uses a filtering expression to decide which rows are visible; a write policy typically uses a check expression to reject rows that should never be inserted or updated.
  3. Rely on the session context. Policies usually evaluate something about the current session — the database role, or a claim carried by the connection — and compare it to a column on the row, such as the owning organization.

The PostgreSQL row security documentation covers the full syntax and semantics. Two details trip up newcomers. First, RLS is a filter, not a separate gate: it narrows what a permitted query can see, and it does not by itself replace authentication. Second, table owners and superusers typically bypass policies unless configured otherwise, which matters when internal tools connect with privileged accounts.

A third subtlety matters once tables have several policies: multiple permissive policies on the same table combine with logical OR, so a row visible under any permissive policy is visible, period. Restrictive policies, a separate kind, combine with AND to narrow the others. Teams that do not know this build accidental loopholes — one permissive policy quietly widening everything the careful policies closed.

#Common Policy Patterns

Most real-world policies are variations on a few themes:

PatternPolicy ideaTypical useWatch out for
Tenant isolationRow's organization matches the session's organizationMulti-tenant SaaS productsForgetting the check on writes, not just reads
Owner-onlyRow's creator matches the current userPersonal notes, drafts, uploadsShared or transferred records need extra paths
Role-based readVisible to one role, editable by anotherPublic profiles editable by their ownersRole claims must actually reach the database
Team membershipRow visible if the user belongs to the row's teamProjects, workspaces, shared documentsMembership checks add query complexity
Status-basedPublic-status rows visible to all; others restrictedPublishing workflows, approvalsStatus transitions must respect the write policy

Real products usually combine several: a tenant filter as the outer wall, with ownership and status rules layered inside it. A project-management product, for instance, might require the matching tenant on every table, allow team members to see their projects, restrict drafts to their creators, and expose only published deliverables to guest roles — all without a single line of filtering in the application.

#RLS and Multi-Tenant Isolation

For multi-tenant architecture, RLS addresses the hardest requirement: proving that tenant data cannot leak across boundaries regardless of application bugs. The shared-database, shared-schema approach — one set of tables, every row tagged with its tenant — only works if that tag is enforced everywhere, and RLS is the cleanest way to do it.

The honest caveat: RLS complements application-layer authorization; it does not replace it. Something still has to decide what a user's role and permissions are — the domain of role-based access control — and RLS then carries those decisions down to the row level. Teams that skip the application layer entirely end up expressing complex organizational logic as database expressions, which becomes hard to test and evolve.

Products in the business-operating-system category — ScopeOS among them — live or die by this guarantee, because a single cross-tenant leak ends the trust the product is built on.

#Where RLS Shows Up in Modern Stacks

Supabase deserves a specific mention, because it leans on RLS heavily: in Supabase projects the database is exposed directly to client applications, and row level security policies are the primary guard in front of every table. That design makes the security model explicit — your data is only as protected as your policies — which is a strength if you treat policies as production code, with review and tests, and a liability if you do not.

#Performance and Pitfalls

RLS is not free, and its failure modes are quiet:

  • Every query pays the policy cost. Simple comparisons against indexed columns are cheap; policies with subqueries or function calls can dominate planning on large tables.
  • Silent empty results. A policy that filters out everything returns no rows rather than an error, which can look like an application bug. Debugging means checking policies, not just queries.
  • Write paths are easy to under-protect. Teams carefully write read policies and forget the check on inserts and updates, allowing rows into the database that can never be read back.
  • Policies are code. They deserve version control, review, and tests per role and scenario — including the uncomfortable test of what a foreign tenant can see.

If this pattern resonates, it is because it powers real products: ScopeOS is built on the same database-first fundamentals described here, and you can explore the full SCOPE ecosystem.

#The Bottom Line

Row level security enforces data access inside the database, filtering rows by policies that apply to every query from every client. It is the strongest practical guarantee for multi-tenant isolation and a natural pairing with application-level RBAC. Its costs are real: policy evaluation on every query, policies that must be tested like code, and silent results that demand disciplined debugging. Used as one deliberate layer — not the only one — it turns data isolation from a hope into a property of the system.

rlsdatabasesecurity

Frequently asked questions

What is row level security in simple terms?
Row level security is a database feature that controls which individual rows a user can see or change, based on rules defined in the database itself. When a query runs, the database automatically filters the results so only permitted rows are returned, no matter where the query came from. It acts as a safety net that still works when application code has a bug.
Does row level security replace application-level authorization?
No, and treating it as a replacement causes problems. RLS enforces decisions at the data layer, but something still has to decide what a particular user is allowed to do — their role, their permissions, the context of the request. That logic lives in the application or identity layer. The strongest setups use both: the application decides intent, and the database guarantees isolation on every query.
Does row level security slow down a database?
It can. Policies add an evaluation step to every query against a protected table, so the impact depends on how complex the policies are. A simple comparison against an indexed column is usually minor, while policies involving subqueries or function calls can affect planning and speed on large tables. Keep policies simple, index the columns they reference, and test performance under realistic load.