Row Filters
Row filters restrict which rows a developer can access by injecting WHERE clause predicates into queries. Filters are defined per-table within a policy, and the agent rewrites queries transparently — no changes to developer queries or client code are needed. Unlike PostgreSQL RLS, row filters require no database-side configuration and work with ephemeral roles.
Row-level filtering is an Enterprise feature. Contact your organization administrator to enable it.
How It Works
Row filters are structured predicates (column, operator, value) defined on table grants within a policy. When a developer runs a query that touches a filtered table, the agent parses the SQL, injects the appropriate WHERE conditions, and forwards the rewritten query to the database. The developer sees only rows that match all filter conditions.
Filtering is read-and-modify-side: SELECT, UPDATE, and DELETE queries are filtered. INSERT statements are not filtered, consistent with how masking is read-side only.
Creating Row Filters
Row filters are configured as part of a policy in the console. To add a row filter:
- Navigate to the database and open the policy you want to edit.
- In the Row Filters section of a table grant, click Add Filter.
- Select a column, choose an operator, and enter a value.
- Save the policy. The updated filters are pushed to the agent immediately.
The policy editor below shows row filters configured on the public.users and public.orders tables. The public.payments table has no row filters for contrast.
analyst · prod-users (us-east-1)
Version 3 · Updated2026-03-10 14:32:00 UTC
Assignments (3)
| Type | Name | Assigned | |
|---|---|---|---|
| User | [email protected] | 2026-02-20 11:00:00 UTC | |
| User | [email protected] | 2026-02-22 15:30:00 UTC | |
| Group | Data Team | 2026-03-01 09:00:00 UTC |
Operators
Row filters support the following operators. Values are passed as text parameters and PostgreSQL handles type coercion automatically.
| Operator | Example Value | Description |
|---|---|---|
= | active | Column equals the value |
!= | archived | Column does not equal the value |
> | 100 | Column is greater than the value |
< | 1000 | Column is less than the value |
>= | 2024-01-01 | Column is greater than or equal to the value |
<= | 2024-12-31 | Column is less than or equal to the value |
IN | us,eu,apac | Column matches any value in the comma-separated list |
NOT IN | test,staging | Column does not match any value in the list |
BETWEEN | 100,999 | Column is between the two comma-separated bounds (inclusive) |
IS NULL | Column value is NULL (no value needed) | |
IS NOT NULL | Column value is not NULL (no value needed) |
User Variables
Filter values can reference the connected developer's user attributes using the double-brace syntax {{user.NAME}}. User attributes are key/value pairs an org admin defines per user (for example tenant_id, department, region) — there are no built-in variables, so a variable resolves only to an attribute you have created. They are resolved at query time, enabling dynamic per-user filtering without creating separate policies for each developer.
| Use Case | Column | Operator | Value |
|---|---|---|---|
| Tenant isolation | tenant_id | = | {{user.tenant_id}} |
| Department access | department | IN | {{user.departments}} |
When a user variable is used with the IN or NOT IN operator, the attribute value is split on commas to produce the list. With BETWEEN, both bounds can be user variables.
An attribute can define a default value used when a particular user has no explicit value set. Resolution is fail-closed: only when a referenced attribute is missing and has no default is the query denied with a permission error. This prevents accidental data exposure from misconfigured attributes — a developer whose attribute is unset sees no rows rather than all rows.
How Filters Combine
Within a policy: multiple filters on the same table are combined with AND. A row must match all filters to be returned. For example, two filters on the orders table — region = us AND status = active — return only active US orders.
Across policies: per-policy predicates are combined with OR. A user assigned to multiple policies sees the union of rows from each policy, consistent with how table grants use a union model.
Bypass flag: a table grant with row filter bypass enabled disables all row filters for that table across all contributing policies. A policy that grants access to a table but defines no row filters also provides unfiltered access — the unfiltered path wins under the union model.
Example: Policy A filters orders to region = us. Policy B filters orders to region = eu. A user assigned to both policies sees orders from both regions (us OR eu). Adding a third policy with no filters on orders gives the user unfiltered access to all orders.
Scope and Deployment Notes
Row filters apply to the queries a developer submits directly. Keep the following in mind when designing policies so the filter covers the access paths you intend:
- Filtering applies to SELECT, UPDATE, and DELETE. INSERT statements add new rows and are not filtered — use table grants to control insert access.
- Functions and stored procedures execute inside PostgreSQL and run with their own privileges, so a routine that reads a filtered table is not re-filtered. Avoid granting EXECUTE on routines that read row-filtered tables, or add PostgreSQL row-level security (RLS) on those tables for defense-in-depth.
- Filter values are text-only — complex types (arrays, JSON) work only when PostgreSQL can coerce the text representation.
- Each filter targets a single column. Cross-column comparisons and arbitrary SQL expressions are not supported.
Related
- Policies — policy configuration, table grants, and multi-policy resolution
- Data Masking — column-level masking presets and wildcard patterns