Row-Level Security (RLS)
RLS policies filter rows transparently on every query. No application code changes needed.
CREATE RLS POLICY user_orders ON orders FOR READ USING (customer_id = $auth.id);
CREATE RLS POLICY user_write ON orders FOR WRITE USING (customer_id = $auth.id);
CREATE RLS POLICY admin_bypass ON orders FOR READ USING ($auth.role = 'admin' OR customer_id = $auth.id);
CREATE RLS POLICY org_access ON projects FOR ALL USING (org_id = $auth.org_id);
Policy Types
| Type | Applies to |
READ | SELECT |
WRITE | INSERT, UPDATE, DELETE |
ALL | Both |
Permissive vs Restrictive
Multiple policies: permissive (default, OR-combined) or restrictive (AND-combined):
CREATE RLS POLICY org_filter ON docs FOR READ USING (org_id = $auth.org_id) RESTRICTIVE;
CREATE RLS POLICY not_deleted ON docs FOR READ USING (status != 'deleted') RESTRICTIVE;
Session Context Variables
RLS policies can reference the authenticated session context via $auth.* variables:
| Variable | Type | Description |
$auth.id | string | User ID (from JWT sub or user record) |
$auth.username | string | Username |
$auth.role | string | Current role |
$auth.tenant_id | u64 | Tenant ID of the authenticated user |
$auth.database_id | u64 | Database ID of the session |
Example combining tenant and database scoping:
CREATE RLS POLICY multi_scope ON data FOR READ
USING (tenant_id = $auth.tenant_id AND database_shard = $auth.database_id);
Substitution is fail-closed: if the auth context lacks a required variable (e.g., $auth.database_id is null), the query rejects the row rather than allowing wide-open access.
Cross-Engine Behavior
RLS filters are injected at plan time, before engine dispatch:
- Vector search — unauthorized vectors excluded from results
- Graph traversal — edges to invisible nodes are skipped
- FTS — only matching documents passing RLS are returned
- KV — blocked rows return empty
Managing
SHOW RLS POLICIES;
DROP RLS POLICY user_orders ON orders;