How does row-level security work?
In PostgreSQL, you switch it on for a table with ALTER TABLE ... ENABLE ROW LEVEL SECURITY, then add policies with CREATE POLICY. Each policy is a condition a row must meet to be visible or changeable, such as its tenant_id matching the current tenant. The database applies it to every query from the roles it covers, so a forgotten WHERE clause can’t return another tenant’s rows.
With RLS on and no policies, the default is to deny: no rows are visible or changeable. Superusers and roles with the BYPASSRLS attribute skip it, and so do table owners, unless the table also has FORCE ROW LEVEL SECURITY.
Supabase leans on RLS because apps can query its database from the browser through the Data API. Its documentation says a table in an exposed schema without RLS is readable and writable by any role with a grant on it, and recommends indexing the columns policies filter on.
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
-- Each request sets app.tenant_id, so rows from other tenants stay invisible.
CREATE POLICY tenant_isolation ON documents
USING (tenant_id = current_setting('app.tenant_id'));Why does row-level security matter for a website voice agent?
A voice agent product keeps many companies’ transcripts, knowledge and settings in one database. Keeping those workspaces apart is what stops one company’s customer conversations reaching another, so ask how a vendor enforces it: in the database with RLS, in application code, or both.
Each layer has limits. RLS needs correct policies and a database role that’s subject to them, and application checks need every query to be scoped. Tests that try to read another tenant’s data are the evidence to ask for.
How Kawl Now uses row-level security
Kawl Now keeps its data in a private Postgres schema on Supabase in London, with row-level security switched on for every table. The schema isn’t exposed through Supabase’s Data API, and Supabase’s anon and authenticated roles have no access to it.
The tables have no RLS policies, so an ordinary database role given access would see no rows, although roles that bypass RLS, such as Supabase’s postgres and service_role, aren’t stopped by it. The application’s own database role owns the tables, and owners aren’t subject to RLS by default, so here RLS is a backstop rather than what keeps workspaces apart.
Workspace separation comes from the application. Every management request is tied to the workspace of the token or signed-in account that made it, queries are limited to that workspace, and the automated tests include tenant isolation checks.
Common questions
Does row-level security replace access checks in the application?
It shouldn’t. RLS works only with correct policies and a database role that’s subject to them, so application code should still scope its queries, and tests should check both layers.
Does row-level security slow queries down?
It can, because the policy condition is checked against each row a query considers. Keep policy expressions simple and index the columns they filter on, as Supabase’s documentation recommends.