Filtering records
Query subsets of data using filter operators with AND/OR logic and dynamic variables.
#Filtering records
#Filtering in the app
Every collection page (Data → your collection) has a Filters button next to the search box. Click it, add one or more conditions, and press Apply:
- Pick a field, an operator, and a value. The operators offered match the
field's type — numbers and dates get
>,≥,<,≤and between; text gets contains, starts with and ends with; enums offer their values as a dropdown. - Every type also offers is empty / is not empty.
- Multiple conditions combine with AND. Two conditions on the same field merge — e.g. views between 10 and 100 plus views not equals 50.
- Filters live in the page URL, so a filtered view can be bookmarked or shared, and they survive sorting and pagination.
Tip: filters and text search stack — search narrows within the filtered set.
The rest of this page covers the same filtering through the API.
Filters let you query subsets of records from a collection. Pass them as the filter query parameter (JSON-encoded) or as an object in the SDK.
EmuView supports two filter formats. You can use either one.
#Format 1: MongoDB-style object filter
A nested object where keys are field names and values contain operators:
#HTTP
GET /api/v1/collections/products/records?filter={"status":{"_eq":"published"},"price":{"_gte":10,"_lte":100}}
Authorization: Bearer sk-your-api-key
#SDK
const results = await sdk.collection('products').list({
filter: {
status: { _eq: 'published' },
price: { _gte: 10, _lte: 100 }
}
});
Shorthand equality — you can omit _eq for simple equality checks:
// These are equivalent:
{
status: 'published';
}
{
status: {
_eq: 'published';
}
}
#Format 2: UserFilter array
A flat array of filter objects with explicit field, op, and value properties:
GET /api/v1/collections/products/records?filter=[{"field":"status","op":"eq","value":"published"},{"field":"price","op":"gte","value":10}]
Multiple filters in the array are combined with AND logic.
#All filter operators
#MongoDB-style operators
| Operator | SQL equivalent | Description | Example |
|---|---|---|---|
_eq |
= |
Equal | { status: { _eq: 'active' } } |
_neq |
!= |
Not equal | { status: { _neq: 'archived' } } |
_gt |
> |
Greater than | { price: { _gt: 50 } } |
_gte |
>= |
Greater than or equal | { price: { _gte: 10 } } |
_lt |
< |
Less than | { stock: { _lt: 5 } } |
_lte |
<= |
Less than or equal | { stock: { _lte: 100 } } |
_in |
IN (...) |
Value in array | { status: { _in: ['active', 'draft'] } } |
_nin |
NOT IN (...) |
Value not in array | { role: { _nin: ['admin'] } } |
_contains |
LIKE '%val%' |
String contains | { title: { _contains: 'widget' } } |
_ncontains |
NOT LIKE '%val%' |
String does not contain | { title: { _ncontains: 'test' } } |
_starts_with |
LIKE 'val%' |
String starts with | { slug: { _starts_with: 'premium' } } |
_nstarts_with |
NOT LIKE 'val%' |
String does not start with | { slug: { _nstarts_with: 'test-' } } |
_ends_with |
LIKE '%val' |
String ends with | { email: { _ends_with: '@acme.com' } } |
_nends_with |
NOT LIKE '%val' |
String does not end with | { email: { _nends_with: '@test.com' } } |
_null |
IS NULL |
Field is null | { deleted_at: { _null: true } } |
_nnull |
IS NOT NULL |
Field is not null | { avatar: { _nnull: true } } |
#UserFilter array operators
| Operator | Description | Example |
|---|---|---|
eq |
Equal | { field: 'status', op: 'eq', value: 'active' } |
neq |
Not equal | { field: 'status', op: 'neq', value: 'draft' } |
gt / gte |
Greater than / or equal | { field: 'price', op: 'gte', value: 10 } |
lt / lte |
Less than / or equal | { field: 'stock', op: 'lt', value: 5 } |
contains |
String contains (LIKE %val%) |
{ field: 'title', op: 'contains', value: 'pro' } |
starts_with |
String starts with | { field: 'slug', op: 'starts_with', value: 'tech-' } |
ends_with |
String ends with | { field: 'email', op: 'ends_with', value: '@acme.com' } |
between |
Value in range (inclusive) | { field: 'price', op: 'between', value: 10, valueTo: 50 } |
in |
Value in comma-separated list | { field: 'status', op: 'in', value: 'draft,published' } |
empty |
Field is NULL or empty string | { field: 'description', op: 'empty' } |
not_empty |
Field is NOT NULL and not empty | { field: 'title', op: 'not_empty' } |
#Logical combinators
Use _and and _or to build complex queries:
// OR: published products or drafts owned by the current user
const results = await sdk.collection('products').list({
filter: {
_or: [
{ status: { _eq: 'published' } },
{
_and: [{ status: { _eq: 'draft' } }, { created_by: { _eq: '$CURRENT_USER' } }]
}
]
}
});
#HTTP
GET /api/v1/collections/products/records?filter={"_or":[{"status":{"_eq":"published"}},{"_and":[{"status":{"_eq":"draft"}},{"created_by":{"_eq":"$CURRENT_USER"}}]}]}
Authorization: Bearer sk-your-api-key
#Dynamic variables
These server-side variables are resolved at query time:
| Variable | Resolves to |
|---|---|
$CURRENT_USER |
The authenticated user's ID |
$CURRENT_ROLE |
The authenticated user's role name |
$PROJECT_ID |
The current project ID |
$NOW |
Current Unix timestamp (seconds) |
$NOW_DATE |
Current date as ISO string (YYYY-MM-DD) |
The three crew variables —
$CURRENT_USER_CREWS,$CURRENT_USER_CREW_EDITORSand$CURRENT_USER_CREW_ADMINS— have been removed along with crew scoping. A filter still naming one is refused rather than resolved to an empty list: "matches nothing" becomes "matches everything" under_neq,_ninand_not, so a rule meant to confine a caller to their crews would have opened the whole collection. A crew reaches a record through a share now.
#Common filter patterns
#Products within a price range
const results = await sdk.collection('products').list({
filter: { price: { _gte: 10, _lte: 100 } }
});
#Records created in the last 24 hours
const yesterday = Math.floor(Date.now() / 1000) - 86400;
const results = await sdk.collection('orders').list({
filter: { created_at: { _gte: yesterday } },
sortBy: '-created_at'
});
#Records belonging to the current user
const myPosts = await sdk.collection('posts').list({
filter: { created_by: { _eq: '$CURRENT_USER' } }
});
#Exclude archived records with a search term
const results = await sdk.collection('products').list({
filter: { status: { _neq: 'archived' } },
search: 'widget',
limit: 25
});
#Pagination with filters
Filters work with both offset-based and cursor-based pagination:
// Offset-based
const page2 = await sdk.collection('products').list({
filter: { status: 'published' },
page: 2,
limit: 25
});
// Cursor-based (more efficient for large datasets)
const firstPage = await sdk.collection('products').list({
filter: { status: 'published' },
limit: 50
});
const secondPage = await sdk.collection('products').list({
filter: { status: 'published' },
limit: 50,
cursor: firstPage.cursors?.next
});