Filtering
When you use a field as an input to a list action, Keel generates a structured query input for that field. Instead of passing a plain value, you pass an object with one or more operators that describe how to filter.
model Product {
fields {
name Text
price Decimal
releaseDate Date
}
actions {
list listProducts(name?, price?, releaseDate?)
}
}// POST /api/json/listProducts
{
"where": {
"name": { "contains": "Widget" },
"price": { "greaterThan": 10.00 },
"releaseDate": { "after": "2024-01-01" }
}
}Each field type has its own set of available operators. All operators are optional, and when multiple are provided they are AND'd together. The full set of operators for each type is documented below.
Text
Text fields (including Markdown) support exact matching, partial matching, and set membership.
| Operator | Type | Description |
|---|---|---|
equals | string | null | Exact match. Pass null to find records where the field is null. |
notEquals | string | null | Excludes exact match. Pass null to find records where the field is not null. |
startsWith | string | Matches values that start with the given string. |
endsWith | string | Matches values that end with the given string. |
contains | string | Matches values that contain the given string. |
oneOf | string[] | Matches any of the provided values. |
// Find products with "Pro" in the name, excluding a specific one
{
"name": {
"contains": "Pro",
"notEquals": "Pro Max Legacy"
}
}Number
Number fields support exact matching, comparison, and set membership.
| Operator | Type | Description |
|---|---|---|
equals | number | null | Exact match. |
notEquals | number | null | Excludes exact match. |
lessThan | number | Matches values strictly less than. |
lessThanOrEquals | number | Matches values less than or equal to. |
greaterThan | number | Matches values strictly greater than. |
greaterThanOrEquals | number | Matches values greater than or equal to. |
oneOf | number[] | Matches any of the provided values. |
// Find products with stock between 10 and 100
{
"stock": {
"greaterThanOrEquals": 10,
"lessThanOrEquals": 100
}
}Decimal
Decimal fields support the same operators as Number.
| Operator | Type | Description |
|---|---|---|
equals | number | null | Exact match. |
notEquals | number | null | Excludes exact match. |
lessThan | number | Matches values strictly less than. |
lessThanOrEquals | number | Matches values less than or equal to. |
greaterThan | number | Matches values strictly greater than. |
greaterThanOrEquals | number | Matches values greater than or equal to. |
oneOf | number[] | Matches any of the provided values. |
Boolean
Boolean fields support equality checks only.
| Operator | Type | Description |
|---|---|---|
equals | boolean | null | Exact match. |
notEquals | boolean | null | Excludes exact match. |
// Find all active products
{
"isActive": {
"equals": true
}
}Date
Date fields support exact matching, range comparisons, and relative date expressions. Values are in ISO 8601 format (YYYY-MM-DD).
| Operator | Type | Description |
|---|---|---|
equals | string | null | Exact date match. |
notEquals | string | null | Excludes exact date match. |
before | string | Matches dates strictly before. |
onOrBefore | string | Matches dates on or before. |
after | string | Matches dates strictly after. |
onOrAfter | string | Matches dates on or after. |
beforeRelative | string | Matches dates before a relative period. |
afterRelative | string | Matches dates after a relative period. |
equalsRelative | string | Matches dates within a relative period. |
// Find orders placed in Q1 2024
{
"orderDate": {
"onOrAfter": "2024-01-01",
"before": "2024-04-01"
}
}Relative date expressions
The beforeRelative, afterRelative, and equalsRelative operators accept a string expression in the following format:
attribute value complete period
v v v v
{this/next/last} {x}? {complete}? {minute/hour/day/week/month/year}| Component | Usage |
|---|---|
| Attribute | this, next, or last |
| Value | A positive integer. Must be omitted when using this. Defaults to 1 when omitted with next or last. |
| Complete | complete — when included, the period is a calendar-aligned boundary. For example, last month is a rolling 30-day window, while last complete month is the previous calendar month. |
| Period | minute, hour, day, week, month, or year. Plural forms are also accepted. |
There are also a few shorthands:
| Shorthand | Equivalent |
|---|---|
now | The current moment |
today | this day |
tomorrow | next complete day |
yesterday | last complete day |
Relative dates are timezone dependent and default to UTC. Use the Time-Zone header to specify the user's timezone. The value must be a IANA time zone (opens in a new tab) string. If using the generated client, it uses the user's timezone automatically.
Timestamp
Timestamp fields support range comparisons and relative expressions. Values are in ISO 8601 format with time (YYYY-MM-DDTHH:mm:ss.sssZ).
| Operator | Type | Description |
|---|---|---|
before | string | Matches timestamps strictly before. |
after | string | Matches timestamps strictly after. |
beforeRelative | string | Matches timestamps before a relative period. |
afterRelative | string | Matches timestamps after a relative period. |
equalsRelative | string | Matches timestamps within a relative period. |
// Find records created in the last 7 days
{
"createdAt": {
"afterRelative": "last 7 days"
}
}Unlike Date, Timestamp does not support equals, notEquals, onOrBefore, or onOrAfter. Use before and after to define ranges.
Duration
Duration fields support exact matching and comparisons. Values are in ISO 8601 duration format (opens in a new tab) (e.g. P1D for one day, PT2H30M for two hours and thirty minutes).
| Operator | Type | Description |
|---|---|---|
equals | string | null | Exact match. |
notEquals | string | null | Excludes exact match. |
lessThan | string | Matches durations strictly less than. |
lessThanOrEquals | string | Matches durations less than or equal to. |
greaterThan | string | Matches durations strictly greater than. |
greaterThanOrEquals | string | Matches durations greater than or equal to. |
// Find tasks with estimated time under 2 hours
{
"estimatedTime": {
"lessThan": "PT2H"
}
}ID
ID fields support exact matching and set membership.
| Operator | Type | Description |
|---|---|---|
equals | string | null | Exact match. |
notEquals | string | null | Excludes exact match. |
oneOf | string[] | Matches any of the provided IDs. |
Enum
Enum fields support exact matching and set membership. Values are the enum member names as strings.
| Operator | Type | Description |
|---|---|---|
equals | string | null | Exact match. |
notEquals | string | null | Excludes exact match. |
oneOf | string[] | Matches any of the provided values. |
enum Status {
Draft
Published
Archived
}
model Post {
fields {
status Status
}
actions {
list listPosts(status?)
}
}// Find posts that are either Draft or Published
{
"where": {
"status": {
"oneOf": ["Draft", "Published"]
}
}
}Null checks
For fields that are optional in your schema, the equals and notEquals operators accept null. This lets you filter for records where a field has or has not been set.
model Task {
fields {
completedAt Timestamp?
}
actions {
list listTasks(completedAt?)
}
}{
"where": {
"completedAt": {
"equals": null
}
}
}Array fields
When a field is an array type (e.g. Text[], Number[]), the query input has a different structure. At the top level you can check if the entire array exactly matches or doesn't match a set of values. You can also use any and all to filter based on individual elements.
| Operator | Type | Description |
|---|---|---|
equals | T[] | null | The array exactly equals the provided values. |
notEquals | T[] | null | The array does not exactly equal the provided values. |
any | object | At least one element in the array matches the nested condition. |
all | object | Every element in the array matches the nested condition. |
The operators available inside any and all are the same as those for the corresponding scalar type, except that equals and notEquals inside any/all are not nullable, and oneOf is not available.
model Product {
fields {
tags Text[]
scores Number[]
}
actions {
list listProducts(tags?, scores?)
}
}// Find products where any tag contains "sale"
// and all scores are greater than 80
{
"where": {
"tags": {
"any": {
"equals": "sale"
}
},
"scores": {
"all": {
"greaterThan": 80
}
}
}
}Non-filterable types
The following field types cannot be used as inputs to list actions and do not have query inputs:
- Secret — encrypted fields
- Password — hashed fields
- File — binary file fields
- Vector — embedding vectors