Skip to content

$filter ​

The $filter query option restricts the set of returned entities. The filter expression is parsed into an AST and translated to SQL WHERE clauses by the resolver.

Syntax ​

GET /odata/Products?$filter=price gt 10
GET /odata/Products?$filter=name eq 'Widget'

Comparison operators ​

OperatorMeaningExample
eqEqual$filter=name eq 'Widget'
neNot equal$filter=name ne 'Widget'
gtGreater than$filter=price gt 10
geGreater than or equal$filter=price ge 10
ltLess than$filter=price lt 100
leLess than or equal$filter=price le 100
inIn list$filter=status in ('active','pending')

Logical operators ​

OperatorMeaningExample
andLogical AND$filter=price gt 10 and active eq true
orLogical OR$filter=origin eq 'lhr' or origin eq 'jfk'
notLogical NOT$filter=not contains(name,'test')

String functions ​

FunctionSQL mappingExample
contains(prop, 'val')LIKE '%val%' ESCAPE '!'$filter=contains(name,'Widget')
startswith(prop, 'val')LIKE 'val%' ESCAPE '!'$filter=startswith(name,'Wid')
endswith(prop, 'val')LIKE '%val' ESCAPE '!'$filter=endswith(name,'get')

The value is matched literally: % and _ are escaped, so contains(label,'50%') finds 50% off and not 500 units.

Case-insensitive matching ​

tolower() and toupper() wrap a property or a string literal, in any comparison and in the three functions above. They translate to LOWER() / UPPER(), so the comparison folds case whatever the column's collation:

$filter=tolower(name) eq tolower('Widget')   → WHERE LOWER(name) = 'widget'
$filter=contains(tolower(name),'widget')     → WHERE LOWER(name) LIKE '%widget%' ESCAPE '!'

This is what UI5 sends for a filter with caseSensitive: false.

Lambda operators ​

any and all filter a collection by a predicate on its related entities. Eloquent-backed sets only — see Coverage by resolver below.

GET /odata/Flights?$filter=passengers/any(p:p/name eq 'Alice')
GET /odata/Flights?$filter=passengers/all(p:p/checkedIn eq true)
OperatorMeaningEloquent translation
anyat least one related entity matcheswhereHas()
allevery related entity matcheswhereDoesntHave() with the negated predicate

all is expressed as "no related entity fails the predicate", which is also how it treats an empty collection: a Flight with no passengers satisfies passengers/all(...).

Null handling ​

$filter=description eq null       → WHERE description IS NULL
$filter=description ne null       → WHERE description IS NOT NULL

Precedence and grouping ​

Use parentheses to control precedence:

$filter=(price gt 10 and price lt 100) or name eq 'Special'

Without parentheses, and binds tighter than or.

Coverage by resolver ​

The two built-in translators implement the same subset of the filter grammar, except for lambdas:

ConstructFilterToEloquent (discoverModel)FilterToQuery (SQL / AbstractEntitySet)
eq ne gt ge lt leyesyes
and or notyesyes
inyesyes
null comparison (eq/ne only)yesyes
contains startswith endswithyesyes
tolower toupper around a property or a stringyesyes
A Boolean property or true/false on its ownyesyes
any / allyesrefused (501): a SQL source has no relations
Arithmetic (add sub mul div mod)refusedrefused
Other functions (length, concat, year(), …)refusedrefused
has (enum flags)refusedrefused

Custom resolvers receive the parsed AST and may implement any of it — see Custom Resolvers.

What is not supported is refused ​

A construct the translator cannot translate is answered with an error instead of being dropped. A filter that silently does not filter would return rows the caller meant to exclude, or none at all:

SituationStatusCode
Function, operator or lambda the translator does not implement501unsupported_filter
Comparison without a property on either side (1 eq 1)400invalid_filter
null compared with anything but eq/ne400invalid_filter
A non-Boolean property or literal on its own400invalid_filter

The message names the construct. The error arrives with its own status code, because the query is run before the response starts streaming.

$filter is what the client asks for, not what the server enforces

Never rely on $filter to scope a read to what a caller may see. Scope the entity set itself (a custom entity set whose query() applies the restriction, or a read authorizer).