$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
| Operator | Meaning | Example |
|---|---|---|
eq | Equal | $filter=name eq 'Widget' |
ne | Not equal | $filter=name ne 'Widget' |
gt | Greater than | $filter=price gt 10 |
ge | Greater than or equal | $filter=price ge 10 |
lt | Less than | $filter=price lt 100 |
le | Less than or equal | $filter=price le 100 |
in | In list | $filter=status in ('active','pending') |
Logical operators
| Operator | Meaning | Example |
|---|---|---|
and | Logical AND | $filter=price gt 10 and active eq true |
or | Logical OR | $filter=origin eq 'lhr' or origin eq 'jfk' |
not | Logical NOT | $filter=not contains(name,'test') |
String functions
| Function | SQL mapping | Example |
|---|---|---|
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)| Operator | Meaning | Eloquent translation |
|---|---|---|
any | at least one related entity matches | whereHas() |
all | every related entity matches | whereDoesntHave() 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 NULLPrecedence 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:
| Construct | FilterToEloquent (discoverModel) | FilterToQuery (SQL / AbstractEntitySet) |
|---|---|---|
eq ne gt ge lt le | yes | yes |
and or not | yes | yes |
in | yes | yes |
null comparison (eq/ne only) | yes | yes |
contains startswith endswith | yes | yes |
tolower toupper around a property or a string | yes | yes |
A Boolean property or true/false on its own | yes | yes |
any / all | yes | refused (501): a SQL source has no relations |
Arithmetic (add sub mul div mod) | refused | refused |
Other functions (length, concat, year(), …) | refused | refused |
has (enum flags) | refused | refused |
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:
| Situation | Status | Code |
|---|---|---|
| Function, operator or lambda the translator does not implement | 501 | unsupported_filter |
Comparison without a property on either side (1 eq 1) | 400 | invalid_filter |
null compared with anything but eq/ne | 400 | invalid_filter |
| A non-Boolean property or literal on its own | 400 | invalid_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).