ActiveManage Docs ← Back to activemanage.co.uk

Row-level Security

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.

Screenshot of the Row-Level Security configuration showing several rules attached to different security groups, each with a match column and a context-expression value

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_id if you use owner-based rules.
  • Index department (or whatever segmentation column) for department-based rules.
  • Index tenantid for 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.