Row-level security restricts which rows a user can see or modify within a datastore. Datastore-level permission says "can interact with this table"; row-level adds "and only these specific rows".
This is the platform's tool for multi-tenant data (one shared table, each tenant sees their own rows), owner-based access (users see only their own records), and department-segmented data (each team sees only their team's records).
Enabling row-level security
On the datastore's edit page, toggle Row-Level Security on. Then configure rules that decide which rows each group can access — typically based on a column on the row matching a property of the logged-in user.
Each rule has three parts:
- Security group the rule applies to.
- Match column on the row.
- Match value — usually a context expression like
{{currentUserID}}or{{currentTenantID}}that's evaluated per request.
If any of a user's groups has a matching rule, they can see the row. If no rule matches, they can't.
Three common rule patterns
Pattern 1: Owner-based
Rows where owner_id = current_user_id. Users can only see records they themselves created. Typical for personal data — a user's own bookings, their own messages, their own profile.
Pattern 2: Department-based
Rows where department = current_user_department. Useful when one shared table serves multiple business units that shouldn't see each other's data — e.g. customer records partitioned by sales region.
Pattern 3: Tenant-based
Rows where tenantid = current_tenant_id. This is handled automatically by the platform's multi-tenancy layer when multi-tenancy is on — every query is auto-filtered by the current tenant. Explicit rules are needed only for cross-tenant exceptions (admins who can see all tenants).
How rules combine
If a user belongs to multiple security groups, each with their own row-level rule, the resulting rules are OR-combined. So a user in both "Department A" and "Department B" groups would see rows where department = A OR department = B.
Combined with the union nature of group permissions, that's how you express hierarchical access. A manager who's in both the "Sales Team" group (rule: owner_id = current_user_id) and the "Sales Manager" group (rule: department = current_user_department) sees both their own records AND every other Sales rep's records in their department.

Performance considerations
Row-level security adds a WHERE clause to every query against the datastore. For tables with millions of rows, make sure the column used in the security rule is indexed — otherwise every read becomes a slow table scan.
Specifically:
- Index
owner_idif you use owner-based rules. - Index
department(or whatever segmentation column) for department-based rules. - Index
tenantidfor multi-tenant — the platform usually does this automatically when you turn on multi-tenancy.
The override role
Often you want one role (admins, support staff) that can see all rows regardless. Two approaches:
Approach 1: Don't configure a rule for the admin group
If a security group has no row-level rule on a datastore, they see all rows (subject to datastore-level read permission). So just leave the admin group's rule blank.
Approach 2: Configure a permissive rule
Explicit rules can be set to always-true (e.g. 1=1) to grant unrestricted access. Useful when you want the rule list to document the policy explicitly.
Warning: Row-level security applies to the auto-generated forms, browse views, and API. If you write custom SQL via Custom Query Views or in raw PHP, the platform doesn't automatically inject the row-level filter — you're responsible for applying it in your code.