A row-level filter narrows what a role can access; it never grants access. A role still needs the base Read / Update / Delete permission on the table. Without the base permission, the filter does nothing.
Opening the Custom Filter
1
Go to Role-Based Access
Backend Console → App Services → Role-Based Access, then click the role you want to scope.
2
Open the Filter for a table
In the permission matrix, find the table’s row and open its Custom Filter. A small JSON editor appears.
3
Paste your filter and Save
Paste the JSON (see the shapes below) and click Save. It takes effect on the next request for that role.
The two modes: applyAs
The Custom Filter has an applyAs field that decides how the filter is used:
To scope rows, set
"applyAs": "row_filter". If you leave applyAs out, the filter behaves as a gate (the existing default) — so adding row-level scoping is always an explicit choice and never changes existing filters.
Filter shape
A row-level filter is a JSON object with four keys:Field rules
Each entry inrules is a single condition on one column:
Operators
in and not_in use values (a list). Every other operator uses value (a single value).Dynamic values (the signed-in user)
Instead of a fixed value, a rule can reference the authenticated user so the same filter scopes each user to their own records:Combining rules: logic
logic controls how multiple rules combine:
"AND"— a row must match every rule."OR"— a row must match at least one rule.
Multiple roles combine as a union
If a user holds several roles and more than one has a row-level filter on the same table, the results are the union (OR) of all their filters. A user is never more restricted for having an extra role — each role’s filter can only add rows they’re allowed to see. Example: rolestylist filters appointments to status = PENDING, role receptionist filters to status = IN_PROGRESS. A user with both roles sees appointments that are PENDING or IN_PROGRESS.
Where the filter applies
Once attached to a role’s table permission, a row-level filter is enforced on:Writes are scoped too
The filter isn’t only for reads — it also protects updates and deletes, so a role can never modify or remove a record it isn’t allowed to see. This holds for single-record and bulk operations, and you don’t change the mutation at all — the scope is applied automatically. Take a role scoped to its own orders:Examples
Users see only their records
Only active, recent records
Domain-scoped by email
Everything except archived
Rules & limits
- Values are always bound safely. Nothing you type in
value/valuesis ever executed as code, so filters can’t be used for injection. - Match your value types exactly:
- Enums / status columns are case-sensitive. If the stored value is
PENDING, filter on"PENDING", not"pending". - Numbers go without quotes (
"value": 100, not"value": "100"), otherwise they’re treated as text. - Booleans use
true/false(no quotes).
- Enums / status columns are case-sensitive. If the stored value is
in/not_inneedvalues(a list); every other operator needsvalue.- Unknown column or operator ⇒ the filter is rejected safely. If a rule references a column that doesn’t exist or an operator that isn’t supported, the role sees no rows for that table (fail-safe) rather than accidentally exposing everything. Double-check
fieldandoperatorspelling if a filter returns nothing unexpectedly.
Troubleshooting
The role still sees all rows
The role still sees all rows
- Confirm the filter is saved on the correct role, the correct table, and that
applyAsis exactly"row_filter". - You may be querying as a project owner / administrator, which is not scoped by design. Test with a user that only holds the filtered role.
- Check the base Read permission is enabled for that table (the filter narrows Read; it doesn’t replace it).
The role sees no rows (but you expected some)
The role sees no rows (but you expected some)
- Value mismatch is the most common cause — usually enum casing (
"PENDING"vs"pending") or a number sent as text. - A
$user.id/$user.emailtoken that doesn’t match how the column stores the user returns nothing. Use the token whose value matches your column. - A misspelled
fieldoroperatormakes the filter fail safe to no rows.
A numeric filter returns nothing
A numeric filter returns nothing
That’s usually correct, not a bug:
greater_than: 10.5 returns nothing if every row is 10.2. Flip the threshold (e.g. greater_than: 10) to confirm the filter is applied — matching rows should reappear.Changes to a role’s filter take effect within a short window (typically under a minute) as the new rules propagate. If a change doesn’t seem to apply immediately, wait a moment and retry.