Skip to Content
ConnectFilter Rules

Filter Rules

Filter rules control which items are returned in a query. They are used in:

  • Items API?filter[field][operator]=value
  • Permissions — restrict which rows a role can access
  • Workflow assignments — determine which items match a workflow

Syntax

Filters follow this pattern:

filter[<field>][<operator>]=<value>

For nested (relational) fields, use dot notation:

filter[author.status][_eq]=active

As a JSON object (for POST bodies or permissions config):

{ "field": { "_operator": "value" } }

Shorthand Equality

When a filter field value is a primitive (string, number, boolean) instead of an operator object, it is treated as _eq:

{ "status": "published" }

is equivalent to:

{ "status": { "_eq": "published" } }

Comparison Operators

OperatorDescriptionExample
_eqEqualsfilter[status][_eq]=published
_neqNot equalsfilter[status][_neq]=draft
_ltLess thanfilter[views][_lt]=100
_lteLess than or equalfilter[views][_lte]=100
_gtGreater thanfilter[views][_gt]=1000
_gteGreater than or equalfilter[created_at][_gte]=2024-01-01
_inIn arrayfilter[status][_in]=draft,published
_ninNot in arrayfilter[status][_nin]=archived
_nullIs nullfilter[deleted_at][_null]=true
_nnullIs not nullfilter[published_at][_nnull]=true
_containsContains substring (case-sensitive)filter[title][_contains]=hello
_icontainsContains substring (case-insensitive)filter[title][_icontains]=hello
_ncontainsDoes not contain (case-sensitive)filter[title][_ncontains]=spam
_nicontainsDoes not contain (case-insensitive)filter[title][_nicontains]=spam
_starts_withStarts with (case-sensitive)filter[slug][_starts_with]=2024-
_istarts_withStarts with (case-insensitive)filter[slug][_istarts_with]=hello
_nstarts_withDoes not start with (case-sensitive)filter[slug][_nstarts_with]=draft-
_nistarts_withDoes not start with (case-insensitive)filter[slug][_nistarts_with]=draft-
_ends_withEnds with (case-sensitive)filter[email][_ends_with]=@example.com
_iends_withEnds with (case-insensitive)filter[email][_iends_with]=@example.com
_nends_withDoes not end with (case-sensitive)filter[email][_nends_with]=@spam.com
_niends_withDoes not end with (case-insensitive)filter[email][_niends_with]=@spam.com
_betweenBetween two values (inclusive)filter[price][_between]=10,100
_nbetweenNot between two valuesfilter[price][_nbetween]=10,100
_emptyEmpty, null, or undefinedfilter[bio][_empty]=true
_nemptyNot empty, not null, and not undefinedfilter[bio][_nempty]=true
_regexMatches regular expressionfilter[slug][_regex]=^2024-

_regex limitations: Uses PostgreSQL regex flavor (not JavaScript regex). In the database layer, it relies on the match operator which may require the pgcrypto extension on some Supabase plans. In the TypeScript evaluator (used for permission checks), it supports both wrapped /pattern/ syntax and raw pattern strings.

_icontains on arrays: When used in the TypeScript evaluator (permission checks), _icontains also scans array values, searching for the substring in any array element.

Logical Operators

Combine multiple conditions with _and or _or:

{ "_or": [ { "status": { "_eq": "published" } }, { "author": { "_eq": "$CURRENT_USER" } } ] }
{ "_and": [ { "status": { "_eq": "published" } }, { "views": { "_gte": 100 } } ] }

As query parameters (URL-encoded):

?filter[_and][0][status][_eq]=published&filter[_and][1][views][_gte]=100

OR-condition operator support is limited. The following operators are supported in _or clauses: _eq, _neq, _gt, _gte, _lt, _lte, _in, _contains, _icontains, _starts_with, _istarts_with, _ends_with, _iends_with, _null, _nnull. Other operators (e.g., _ncontains, _between, _regex, _empty, _nin, and all negated case-insensitive variants) are silently ignored in _or clauses. Use _and with individual conditions or JSON notation if you need these operators.

Relational Filters

Filter by fields on related records using dot notation:

?filter[author.status][_eq]=active ?filter[tags.name][_in]=javascript,typescript ?filter[comments.approved][_eq]=true

Supported relation types: M2O (many-to-one), M2M (many-to-many), O2M (one-to-many).

Relational filters generate a subquery or JOIN on the related table. For M2M relations, the filter applies to any matching row in the junction table. The join type depends on whether _has is also specified — see below.

_has Operator

The _has operator controls whether parent rows with no matching children are included in results. It converts a left-outer join to an inner join, so only rows with at least one matching related record are returned.

Standalone usage — only return items that have at least one related role:

{ "roles": { "_has": true } }

Combined with conditions — only return items that have at least one role with status=active:

{ "roles": { "_has": true, "status": { "_eq": "active" } } }

Without _has, a left-outer join is used: parent rows are returned even if no related rows match the filter conditions.

Dynamic Variables

Use these special values in permission rules and API queries to create row-level access control:

VariableResolves to
$CURRENT_USERThe authenticated user’s ID
$CURRENT_USER.fieldA field value from the current user’s profile (e.g., $CURRENT_USER.team_id)
$CURRENT_USER.relation.fieldA nested relation value from the current user (M2M supported)
$CURRENT_ROLEThe current user’s primary role ID
$CURRENT_ROLESArray of all role IDs the current user holds
$CURRENT_POLICIESArray of all policy IDs the current user holds
$NOWCurrent timestamp
$CURRENT_RESOURCE_URIThe resource URI scope of the current request

Example — let users only read their own records:

{ "user_id": { "_eq": "$CURRENT_USER" } }

Example — restrict to items belonging to the current user’s team:

{ "team_id": { "_eq": "$CURRENT_USER.team_id" } }

Array Values

For _in, _nin, _between, and _nbetween, pass a comma-separated string or a JSON array:

?filter[status][_in]=draft,published
{ "status": { "_in": ["draft", "published"] } }
Last updated on