Row-level security filters database rows by user permissions directly in PostgreSQL. From multi-tenant isolation and RBAC integration to Supabase policies: learn how RLS enforces data security at the database level.
Row-Level Security (RLS) is a built-in database feature in PostgreSQL that controls at the row level which data a user can read, create, update, or delete. Through declarative policies, access rules are defined per table that are automatically and transparently enforced on every query, regardless of the source of the request. RLS operates at the database level, fully independent of application code, making it a fundamental defense layer for data isolation in multi-tenant SaaS applications and compliance with privacy regulations like GDPR.

Row-Level Security (RLS) is a built-in database feature in PostgreSQL that controls at the row level which data a user can read, create, update, or delete. Through declarative policies, access rules are defined per table that are automatically and transparently enforced on every query, regardless of the source of the request. RLS operates at the database level, fully independent of application code, making it a fundamental defense layer for data isolation in multi-tenant SaaS applications and compliance with privacy regulations like GDPR.
RLS in PostgreSQL is activated per table with ALTER TABLE tablename ENABLE ROW LEVEL SECURITY. Once enabled, the default policy is deny-all: no rows are visible unless an explicit policy grants access. Policies are created with CREATE POLICY and can be scoped to specific operations: FOR SELECT, FOR INSERT, FOR UPDATE, and FOR DELETE. A typical multi-tenant policy filters on tenant_id: CREATE POLICY tenant_isolation ON orders USING (tenant_id = current_setting('app.tenant_id')::uuid). The USING clause filters which existing rows are visible, while WITH CHECK validates which rows may be inserted or modified. The user context is typically set via SET LOCAL or set_config() at the beginning of each request. In Supabase, the tenant_id is automatically derived from the JWT token of the authenticated user. The auth.uid() function returns the user ID and auth.jwt() provides access to custom claims in the token. RLS integrates seamlessly with Role-Based Access Control (RBAC). Policies can combine conditions: an admin role sees all rows, a user role sees only their own data, and a viewer role sees all organization data but cannot modify anything. This is implemented through OR conditions or multiple policies per table. For performance, it is crucial that the columns referenced in RLS policies are indexed. A policy on tenant_id without an index on that column results in a full table scan on every query. PostgreSQL applies RLS filters as additional WHERE conditions that are optimized by the query planner together with the original query. The BYPASSRLS privilege allows specific database roles to circumvent RLS, which is necessary for administrative tasks like migrations, data exports, and background processes. In Supabase, this is the service_role key that must only be used server-side. For complex authorization scenarios, policies can leverage subqueries and functions. A policy can verify whether a user is a member of a specific team through a subquery on the team_members table. Security definer functions encapsulate complex authorization logic that is reused across multiple policies. This prevents duplication and makes the authorization model centrally manageable from a limited number of functions.
MG Software implements RLS in every multi-tenant SaaS application we build on Supabase. Our standard approach begins with enabling RLS on all tables containing tenant data, combined with a default-deny policy that prevents new tables from being accidentally unprotected after a migration. Policies are based on the tenant_id stored as a custom claim in the Supabase JWT token. Upon authentication, the tenant context becomes automatically available via auth.jwt()->'tenant_id'. This eliminates the need to implement tenant filtering in application code and guarantees isolation regardless of how the data is accessed. For administrative tasks and background processes, we use the Supabase service_role key that bypasses RLS. This key is used exclusively server-side in protected API routes and background processes, never in client-side code. For every new table, we document the expected RLS policies in the migration script and test them with automated tests that execute queries from different user contexts to detect cross-tenant leaks.
Row-Level Security provides a defense layer for data isolation that functions independently of application code. In multi-tenant SaaS, RLS prevents bugs, API errors, or incorrect queries from accidentally exposing data belonging to other tenants. This is not merely a best practice but a requirement for compliance with regulations like GDPR. During security audits, RLS serves as concrete evidence that data isolation is implemented at the infrastructure level. Without RLS, data isolation depends entirely on correct WHERE clauses in every application query. A single forgotten filter in a new endpoint or an error in an ORM query can lead to a data breach affecting all tenants. RLS shifts this responsibility to the database itself, where it is enforced consistently and automatically regardless of how data is queried, whether through the application, an admin tool, or a direct database connection.
Teams often forget to test RLS policies with different user contexts, leaving permission leaks undetected until a security audit or incident. Write automated tests that execute queries as users from different tenants and systematically validate that cross-tenant data is invisible for both read and write operations. A second common mistake is not setting a default-deny policy when enabling RLS. Without an explicit default-deny, new tables without policies are accessible to all authenticated users. Always establish restrictive policies as the baseline and grant access only through explicit GRANT statements. Additionally, performance implications are underestimated: policies on unindexed columns cause full table scans that progressively slow the database as tables grow in size. Always add an index on the column being filtered in the RLS policy.
The same expertise you're reading about, we put to work for clients.
Discover what we can doWhat Is GDPR? How the EU Privacy Regulation Affects Your Software and Business
GDPR mandates how organizations collect, process, and protect personal data of EU citizens. With fines up to 4% of global revenue, understanding privacy by design, data processing agreements, and technical compliance measures is essential.
OAuth 2.0 Explained: Authorization, Tokens, Scopes, and Secure Login Without Passwords
OAuth 2.0 enables secure access to third-party APIs and applications without sharing passwords. Discover how the authorization protocol behind every "Sign in with Google" flow works, which grant types exist, and how to implement it securely.
What is SSL/TLS? - Definition & Meaning
SSL/TLS encrypts the connection between browser and server via HTTPS, which is essential for data protection, user trust, and search engine rankings.
Financial sector software: fintech platforms, compliance automation, secure portals and legacy modernisation
Regulatory complexity should accelerate innovation, not slow it down. We build financial software with PSD2, MiFID II and DORA compliance embedded from day one, enabling faster onboarding, automated risk workflows and real-time reporting that satisfies both customers and regulators.