GMI Software
Core areas
Mobile Apps
iOS, Android, React Native
Headless & B2B commerce
Stores, sales platforms, ERP/PIM integrations
AI & Automation
Agents and LLM implementations
Complementary services
E-commerce mobile analyticsProduct Discovery & DesignBackend, API & IntegrationsMaintenance & AuditsDDT process
Don't know what to choose? Order a consultation
Our projects
Case studies and references
App Ideas Library
Use case examples
MobileCore Stack
React Native
E-commerceCore Stack
Service: commerce & B2BAdvanced commerceMedusaJS
Frontend & QA
Next.jsReactTypeScriptPlaywrightMaestro
Backend, DB & Cloud
Node.jsNestJSPostgreSQLDockerAWS
E-commerce Innovation
3D configurators (BabylonJS)AI agents & automationRAG & knowledge basesAI-native software companyView all AI services
View all technologies
About us
Our history and values
Careers
Join our team
Contact
Get in touch
Get in touch
Services
Core areas
Mobile Apps
iOS, Android, React Native
Headless & B2B commerce
Stores, sales platforms, ERP/PIM integrations
AI & Automation
Agents and LLM implementations
Complementary services
E-commerce mobile analyticsProduct Discovery & DesignBackend, API & IntegrationsMaintenance & AuditsDDT process
Don't know what to choose? Order a consultation
Projects
Our projects
Case studies and references
App Ideas Library
Use case examples
Technologies
MobileCore Stack
React Native
E-commerceCore Stack
Service: commerce & B2BAdvanced commerceMedusaJS
Frontend & QA
Next.jsReactTypeScriptPlaywrightMaestro
Backend, DB & Cloud
Node.jsNestJSPostgreSQLDockerAWS
E-commerce Innovation
3D configurators (BabylonJS)AI agents & automationRAG & knowledge basesAI-native software companyView all AI services
View all technologies
Company
About us
Our history and values
Careers
Join our team
Contact
Get in touch
Get in touch
Back to blog
Technology
Updated: July 11, 2026· Originally published: March 14, 2026
18 min read

How do you secure multi-tenant SaaS with PostgreSQL RLS?

Mikołaj Lehman, CEO & Founder
Mikołaj Lehman
CEO & Founder

Short answer: PostgreSQL RLS makes sense in pooled multi-tenant SaaS when many customers share tables but every row must be filtered by tenant context set by the application. RLS does not replace NestJS authorization or tests, but it adds a database-engine boundary that protects against a forgotten `WHERE tenant_id = ...`, a faulty ORM query and some admin tooling mistakes.

First problem: multi-tenant SaaS scales cost, but concentrates risk

Multi-tenant SaaS is attractive because one application and one infrastructure footprint serve many customers. Instead of maintaining a separate database for every company, you keep data in shared tables, usually with a `tenant_id` column. Infrastructure cost goes down, migrations are simpler and feature rollout is faster.

But this model has one brutal risk: cross-tenant data leakage. If customer A sees invoices, users, CRM notes or documents from customer B, the problem is not just a bug. It is a security incident, loss of trust, potential legal exposure and a conversation with your largest customer that nobody wants to have.

That is why in B2B systems it is not enough to say "our backend always adds `tenant_id`". One endpoint, report, CSV export, job, admin view or raw query without the predicate is enough. PostgreSQL Row-Level Security does not remove every risk, but it moves part of isolation from developer memory into the database engine.

Pooled, silo or bridge: three tenancy models

Before choosing RLS, choose the tenancy model. Silo means a separate database or schema for a customer. It gives the strongest isolation and easier backup/restore per tenant, but costs more in operations, migrations and automation. Pooled means shared tables and separation through `tenant_id`. It is cheapest at scale, but needs strong guardrails.

Bridge is a compromise: the largest or regulated customers get a separate database/schema, while the long tail runs in a pooled model. This can be sensible in B2B SaaS where enterprise accounts pay for isolation and smaller customers need scale economics.

RLS mainly concerns the pooled model. AWS Prescriptive Guidance explicitly says RLS is required to maintain tenant isolation in a pooled PostgreSQL model and centralizes isolation enforcement at the database level. That does not mean every table looks the same. It means every tenant-data table needs an intentional access policy.

  • Silo: strongest isolation, higher operational cost and per-customer migrations.
  • Pooled: best scale economics, highest need for data-isolation discipline.
  • Bridge: pooled for most customers, silo for enterprise or regulated tenants.
Article graphic: PostgreSQL RLS for multi-tenant SaaS: data isolation, cost and risks
Article graphic: PostgreSQL RLS for multi-tenant SaaS: data isolation, cost and risks

What PostgreSQL Row-Level Security actually does

The official PostgreSQL documentation describes RLS as policies that restrict which rows can be returned, inserted, updated or deleted. Once RLS is enabled, normal access to the table must be allowed by a policy. If no policy exists, default-deny applies: rows are not visible or modifiable.

For SaaS, the idea is simple: an `invoices` table can have a policy like `tenant_id = current_setting('app.current_tenant')::uuid`. After authentication, the application sets session context and the database automatically applies the filter. Even if code forgets `WHERE tenant_id = ...`, a normal user should not see another tenant rows.

This sounds like magic, but it is not. RLS works per table, per role and per command. Table owners and roles with `BYPASSRLS` can bypass policies. That is why production design must cover application roles, `FORCE ROW LEVEL SECURITY`, migration tests and a clear split between application, admin and maintenance connections.

NestJS pattern: tenant context, transaction and reset

The most common mistake is not the RLS policy itself, but setting context. After authorization, the API must know which tenant is making the request and then set that tenant in the PostgreSQL session. In practice, we use NestJS request context, guards/authorization and a database-client layer that does not allow queries without tenant context where it is required.

With connection pooling, be careful: connections are reused. Tenant context should be set for the transaction or request and reset afterwards. In PostgreSQL, teams often use `set_config('app.current_tenant', tenantId, true)` inside a transaction, where the third argument `true` limits the setting to the current transaction.

Whether you use Prisma, Drizzle, Kysely or raw `pg`, the principle is the same: the ORM is not the security boundary. The ORM helps build queries, but RLS needs deliberate connection lifecycle, tests and wrappers that prevent accidental use of a superuser or a connection string that bypasses policies.

Policies a real SaaS product needs

The simplest `USING (tenant_id = current_setting(...))` policy is only the beginning. A production SaaS needs separate rules for `SELECT`, `INSERT`, `UPDATE` and `DELETE`, because row visibility and the right to create/change a row are not the same. PostgreSQL lets you use `USING` for access and `WITH CHECK` for data being written.

Example: a user may view invoices from their tenant, but must not change an invoice `tenant_id` through an API payload. `WITH CHECK` should enforce that a new or updated row still belongs to the current tenant. For shared tables, such as subscription plans, feature flags or public catalogue, the policy may differ or RLS may not be the right boundary.

Roles matter too. The production app should not connect as the table owner or superuser. Separate roles for app runtime, migrations, read-only reporting and break-glass admin reduce the risk that one library or script disables the entire isolation model.

Isolation tests: do not trust a policy until you try to break it

RLS needs negative tests. It is not enough to confirm tenant A can see their invoices. You must confirm tenant A cannot see B invoices, cannot insert a row with B `tenant_id`, cannot update their row to B `tenant_id`, and cannot receive data through a relation, view, SQL function, export, search or background job.

Good tests create two tenants, two users and similarly shaped data, then run scenarios through the API and directly at the database-client layer. Test migrations too: a new table containing tenant data must not reach production without RLS and a `tenant_id` index.

Supabase reminds teams that RLS is powerful, but must be enabled. PostgreSQL reminds teams that once RLS is enabled and no policy exists, default-deny applies. That is an excellent safety property, provided the review process detects tables without policies before customers discover missing data after deployment.

Performance: RLS is not free, but neither is chaos

The common CTO question is: will RLS slow the database down? The answer is: it can, if policies are complex, reference other tables without indexes or require functions evaluated per row. A simple `tenant_id` policy on well-indexed tables is usually far less problematic than manual filters scattered across the codebase.

Design for RLS from the beginning: `tenant_id` as a first-class column on tenant-owned tables, indexes including `tenant_id`, deliberate unique constraints, partitioning only after measurement, separate paths for global tables and no policies that run heavy subqueries per row.

Remember that RLS does not solve every data problem. Analytics, warehouses, events, search indexes and cache need their own tenant isolation. If Elasticsearch or Redis cache receives a key without a tenant prefix, PostgreSQL RLS will not save the layer outside the database.

When RLS is not enough

RLS is a strong guardrail, but not a security strategy by itself. If a customer requires a separate data region, backup/restore, dedicated encryption key, maintenance window or certification boundary, pooled with RLS may not be enough. You need silo or bridge tenancy.

RLS also does not replace business authorization. The fact that a user belongs to a tenant does not mean they can view payroll, export all invoices or delete a project. Roles, subscription plan, feature flags and account status still need to be handled in the application and often reflected in policies.

Do not use RLS as an excuse to skip threat modeling. Analyze administrators, support, integrations, webhooks, background jobs, backups, BI, audit, logs and debugging tools. Leakage can come not only from an API endpoint, but also from a "helpful" export for customer support.

Implementation cost: what actually moves the budget

A multi-tenant SaaS backend with NestJS, PostgreSQL RLS, authorization, a starter console and payments usually starts around PLN 160,000-300,000 for a useful MVP. Advanced B2B SaaS with workflows, audit, billing, integrations, data imports and reporting can go higher. The RLS policy itself is not expensive. The expensive part is confidence that nobody bypasses it.

The biggest cost drivers are data model, roles and permission model, migrations, isolation tests, connection pooling, integrations, audit logs, admin/support tooling, importing existing data and monitoring. If the SaaS has AI/RAG features, tenant isolation must also cover embeddings, retrieval and cache.

GMI provides an initial estimate within 48 hours, but fixed price only after DDT. In DDT we define tenancy model, data map, threat model, MVP scope, RLS policies, tests and maintenance risks. That is more honest than promising "secure SaaS" based on stack names alone.

Pre-development checklist

This checklist should be reviewed by the CTO, product owner and security owner before the first backend sprint.

  • Which tables are tenant-owned, global and admin-only?
  • Does every tenant-owned table have `tenant_id`, an index and an RLS policy?
  • How does the API set tenant context, and is it reset with connection pooling?
  • Which roles can bypass RLS, and does the production app avoid them?
  • Do we have negative tests for SELECT, INSERT, UPDATE, DELETE, exports and background jobs?
  • How do we isolate data in cache, search, events, BI, backups and AI/RAG?
  • Do enterprise tenants require silo or bridge instead of pooled?

How GMI designs this backend

We start with DDT: mapping tenants, roles, data, integrations, billing, admin operations and security risks. Only then do we decide whether the system should be pooled with RLS, silo, bridge or mixed. This matters because a bad tenancy decision can force an expensive migration after the first enterprise customers arrive.

Technically, we usually use NestJS, PostgreSQL, Prisma or another typed query layer, Redis where cache/queue is needed, Next.js for the console and React Native if the product has a mobile surface. RLS is part of a larger architecture: authorization, audit log, observability, contract tests and safe support tooling.

Commercially, the client gets source-code ownership, no vendor lock-in, fixed price after DDT and a clear maintenance model. This is especially important in SaaS because security is not a one-time feature. Every new table, integration and AI feature must keep respecting the tenant boundary.

Sources and further reading

PostgreSQL Row Security Policies: https://www.postgresql.org/docs/current/ddl-rowsecurity.html

AWS row-level security recommendations for multi-tenant PostgreSQL: https://docs.aws.amazon.com/prescriptive-guidance/latest/saas-multitenant-managed-postgresql/rls.html

Supabase Row Level Security guide: https://supabase.com/docs/guides/database/postgres/row-level-security

PostgreSQL runtime configuration and session settings: https://www.postgresql.org/docs/current/runtime-config-client.html

Prisma raw SQL and transactions docs: https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries

GMI NestJS order microservices guide: /blog/nestjs-order-microservices-when-to-split

GMI NestJS, Prisma and PostgreSQL at scale guide: /blog/nestjs-prisma-postgresql-at-scale

GMI AI-native software development: /services/ai-native-software-development

Frequently asked questions

Is PostgreSQL RLS enough to secure multi-tenant SaaS?
Not by itself. RLS is a strong database-level defense-in-depth layer, but you still need application authorization, proper PostgreSQL roles, negative tests, isolation for cache/search/events and migration review. RLS mainly protects PostgreSQL tables, not the entire system.
When should you choose pooled multi-tenant with RLS instead of database-per-customer?
Pooled with RLS makes sense when many customers use a similar product and you need low infrastructure cost, fast migrations and shared feature rollout. Database-per-customer is better for strong regulatory requirements, dedicated backup/restore, separate data region or enterprise isolation.
How does an application set tenant context for RLS?
After authentication, the API determines the user tenant and sets a PostgreSQL session variable such as `app.current_tenant`, usually for the transaction. The RLS policy compares table `tenant_id` with that variable. With connection pooling, context reset matters.
Does Prisma work with PostgreSQL RLS?
Yes, but Prisma is not the security boundary. RLS runs in PostgreSQL, so Prisma queries are subject to policies if you use the right role and set tenant context on the connection/transaction. This needs wrappers, tests and care with raw SQL and migrations.
How much does a multi-tenant SaaS backend with PostgreSQL RLS cost?
A useful MVP with NestJS, PostgreSQL RLS, authorization, console and payments often starts around PLN 160,000-300,000. Advanced B2B SaaS with workflows, audit, integrations and data imports can cost more. We quote fixed price after DDT.
Does RLS protect data in cache, search and AI/RAG?
Not directly. RLS works inside PostgreSQL. Redis, Elasticsearch, events, data warehouses, backups and AI/RAG embeddings need their own tenant isolation: key prefixes, filters, per-tenant indexes or separate resources where risk requires it.

Content updated: July 11, 2026

Share article:

Related articles

Technology

React Native vs native apps in 2026: a business decision, not a technology religion

The right choice does not depend on which framework has louder fans. It depends on product risk: time-to-market, maintenance cost, hardware access, performance, release operations and app-store quality. A practical decision model for CEOs, CTOs and Heads of Product.

Technology

NestJS microservices: when should ecommerce split order processing from a monolith?

A decision guide for CTOs and ecommerce leaders: when to keep the monolith, when to extract order management, and how to use NestJS, RabbitMQ/Redis, Strangler Fig and DDT without checkout risk.

Contact

Let's talk
about the project.

Have an app idea or need technological support? Write to us — we'll prepare a preliminary analysis and estimate within 48h. Projects that go through our DDT process (Discovery, Design & Technology) come with a price guarantee and a fixed-price agreement — a key differentiator for us.

Write to us[email protected]
Visit us
GD
gmi.software Sp. z o.o.ul. Jana Heweliusza 11 / 819
80-890 Gdansk, Poland
NIP: 5252816287KRS: 0000830003
gmi.
ServicesOur projectsBlogBrief assistantContact
LIFAINGI
Mobile Trends Awards 2025 nomination - SFD app
© 2026 gmi.software Sp. z o.o.
Privacy PolicyTerms