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_EDITORS and $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, _nin and _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
});