Filtering & Querying¶
Every list endpoint generated by Resource(...) accepts a rich set of query parameters for filtering, sorting, field selection, and searching. You write no custom handler code — registering the resource is enough.
There are two filter syntaxes, and they compose: suffix operators on query parameters for simple cases, and a JSON where clause for complex boolean logic.
Exact match¶
The simplest filter is a query parameter whose name matches a model field:
Values are type-coerced before being sent to MongoDB:
trueandfalsebecome booleans.nullandnonebecomeNone.- Integer-looking strings become integers.
- Float-looking strings become floats.
- 24-character hex strings become
ObjectIds (so?tenant_id=507f...works as expected). - Everything else stays a string.
Suffix operators¶
Use double-underscore operators for comparisons, ranges, and set membership:
| Operator | Example | MongoDB equivalent |
|---|---|---|
gt |
?age__gt=18 |
{"age": {"$gt": 18}} |
gte |
?price__gte=100 |
{"price": {"$gte": 100}} |
lt |
?age__lt=65 |
{"age": {"$lt": 65}} |
lte |
?price__lte=999 |
{"price": {"$lte": 999}} |
ne |
?status__ne=cancelled |
{"status": {"$ne": "cancelled"}} |
in |
?status__in=paid,pending |
{"status": {"$in": ["paid", "pending"]}} |
nin |
?status__nin=cancelled,expired |
{"status": {"$nin": [...]}} |
exists |
?deleted_at__exists=false |
{"deleted_at": {"$exists": false}} |
regex |
?name__regex=^Ali |
{"name": {"$regex": "^Ali", "$options": "i"}} |
in and nin take a comma-separated list. exists takes true or false. regex is always case-insensitive.
curl "https://api.example.com/products?price__gte=100&price__lt=500&in_stock=true"
curl "https://api.example.com/users?status__in=active,trial&created_at__gte=2026-01-01"
The where clause¶
For conditions that cannot be expressed with flat query parameters — $or branches, nested objects, negation — pass a JSON object in the where query parameter:
curl -G https://api.example.com/products \
--data-urlencode 'where={"status":"active","price":{"$gt":100}}'
curl -G https://api.example.com/orders \
--data-urlencode 'where={"$or":[{"status":"paid"},{"status":"partial"}]}'
curl -G https://api.example.com/users \
--data-urlencode 'where={"email":{"$regex":"^admin@","$options":"i"}}'
The where parameter accepts a strict subset of MongoDB operators. Every operator that appears in the parsed object — at any nesting level — is checked against an allow-list, and unknown operators are rejected with a 400.
Allowed operators:
Operators that execute JavaScript or expose server state — $where, $expr, $function, $accumulator — are never allowed, even if a client tries to embed them. Invalid JSON or a non-object top-level value also returns a 400.
Combining where with query parameters¶
The where clause and suffix-operator parameters merge. Query parameters override matching keys in where if both specify the same field:
Sorting¶
sort accepts one or more comma-separated field names. Prefix a field with - for descending order (leading + is also allowed for explicit ascending).
Without a sort parameter, Craft Easy uses the resource's default_sort (typically -created_at).
Field projection¶
Use fields to return only selected fields — useful for list views that need fewer columns:
_id is always included in the response even if not listed. Field projection applies to both offset and cursor pagination modes.
Full-text search¶
When a resource declares search_fields, the q parameter performs case-insensitive prefix matching across those fields. Each whitespace-separated word in the query must match at least one field — multi-word queries AND the matches together:
Calling ?q=... on a resource without search_fields returns 400 — Search is not enabled for this resource.
Tag filters¶
Models with a tags field pick up three extra filter parameters automatically:
| Parameter | Semantics |
|---|---|
tags__in |
Any tag matches (comma-separated). |
tags__all |
Every listed tag must be present. |
tags__nin |
None of the listed tags may be present. |
Reserved parameters¶
The following query parameters are never treated as filters — they control pagination, sorting, projection, and response shape:
page per_page cursor limit direction
sort fields where q
include_deleted populate
tags__in tags__all tags__nin
Any other parameter is either a field match or a suffix-operator filter.
Example: combining everything¶
curl -G https://api.example.com/orders \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode 'where={"$or":[{"status":"paid"},{"status":"partial"}]}' \
--data-urlencode 'amount__gte=100' \
--data-urlencode 'created_at__gte=2026-01-01' \
--data-urlencode 'customer_name__regex=^Acme' \
--data-urlencode 'sort=-created_at' \
--data-urlencode 'fields=id,reference,amount,status,created_at' \
--data-urlencode 'per_page=50'
Source¶
craft_easy/core/crud/filtering.py—build_filters,parse_where,build_sort,build_search_filter, operator allow-list, and the reserved-parameter set.