Filters
Keep only the documents that match: compare values, search text, match list items, combine conditions, and filter inside groups, arrays, and blocks.
A filter keeps only the documents that match it. Pass it as Where in Go and where in TypeScript
and REST. Examples use the example schema.
Published posts:
page, err := local.List(ctx, "posts", ridu.ListOptions{
Where: query.Equal("status", "published"),
Actor: user,
})const page = await ridu.list('posts', {
where: { status: { equals: 'published' } }
});curl -G "$RIDU/api/collections/posts" \
-H "Authorization: Session $TOKEN" \
--data-urlencode 'where={"status":{"equals":"published"}}'The rest of this page shows only the filter. In TypeScript, filters are typed PostsWhere, which
ridu generate writes from the collection, so an unknown field or operator fails to compile. In
REST, the filter is the where parameter.
Match a value
Posts that are published, and posts that aren’t:
published := query.Equal("status", "published")
notPublished := query.NotEqual("status", "published")const published: PostsWhere = { status: { equals: 'published' } };
const notPublished: PostsWhere = {
status: { notEquals: 'published' }
};where={"status":{"equals":"published"}}
where={"status":{"notEquals":"published"}}equals: null matches a field with no value.
Match one of several values
Posts that are drafts or published:
draftOrPublished := query.In("status", "draft", "published")const draftOrPublished: PostsWhere = {
status: { in: ['draft', 'published'] }
};where={"status":{"in":["draft","published"]}}To exclude several values, wrap in in not.
Compare numbers and dates
Posts with at least 600 views:
popular := query.GreaterThanEqual("views", 600)const popular: PostsWhere = { views: { greaterThanEqual: 600 } };where={"views":{"greaterThanEqual":600}}Posts published since 15 September 2026:
recent := query.GreaterThanEqual("publishedAt", "2026-09-15")const recent: PostsWhere = {
publishedAt: { greaterThanEqual: '2026-09-15' }
};where={"publishedAt":{"greaterThanEqual":"2026-09-15"}}Use greaterThan, greaterThanEqual, lessThan and lessThanEqual on numbers, dates and text.
Write a date in the field’s own format.
Search text
contains matches part of a value, ignoring case. like matches values that contain every word
you give, in any order.
Titles containing “ridu”, and titles with both “sdk” and “typescript”:
aboutRidu := query.Contains("title", "ridu")
sdkGuides := query.Like("title", "sdk typescript")const aboutRidu: PostsWhere = { title: { contains: 'ridu' } };
const sdkGuides: PostsWhere = { title: { like: 'sdk typescript' } };where={"title":{"contains":"ridu"}}
where={"title":{"like":"sdk typescript"}}The first matches “Hello, Ridu”; the second matches “TypeScript SDK tour”. Neither ranks results by relevance.
Check that a field is set
Posts that haven’t been given a publish date:
undated := query.Exists("publishedAt", false)const undated: PostsWhere = { publishedAt: { exists: false } };where={"publishedAt":{"exists":false}}exists: true matches documents where the field has a value. An empty list counts as a value.
Combine conditions
Published posts that are tagged “typescript” or have more than 1,000 views:
featured := query.And(
query.Equal("status", "published"),
query.Or(
query.In("tags", "typescript"),
query.GreaterThan("views", 1000),
),
)const featured: PostsWhere = {
and: [
{ status: { equals: 'published' } },
{
or: [
{ tags: { in: ['typescript'] } },
{ views: { greaterThan: 1000 } }
]
}
]
};{
"and": [
{ "status": { "equals": "published" } },
{
"or": [
{ "tags": { "in": ["typescript"] } },
{ "views": { "greaterThan": 1000 } }
]
}
]
}Posts not tagged “go”:
notAboutGo := query.Not(query.In("tags", "go"))const notAboutGo: PostsWhere = { not: { tags: { in: ['go'] } } };where={"not":{"tags":{"in":["go"]}}}and and or take two or more conditions; not takes one.
Lists and has-many fields
Some fields hold several items: text and number lists, multiple selects, and relationships or
uploads that allow many documents. Filter them with in, which matches a document when any of
its items equals any value you give.
Posts tagged “guide” or “launch”:
guidesOrLaunches := query.In("tags", "guide", "launch")const guidesOrLaunches: PostsWhere = {
tags: { in: ['guide', 'launch'] }
};where={"tags":{"in":["guide","launch"]}}Matching is exact, so “guide” doesn’t match “guidebook”. To require two tags, join two in
filters with and; to exclude a tag, wrap in in not. These fields also accept exists and
equals: null. Any other operator is refused, rather than matching nothing.
A has-many relationship takes document IDs: { authors: { in: [adaID] } } finds posts that list Ada
among their authors. A polymorphic relationship can point into several collections, so each value
names one: { subjects: { in: [{ relationTo: 'posts', id: postID }] } }, or
query.Reference("posts", postID) in Go.
Fields in groups, arrays and blocks
Reach a nested field with a dotted path of field names.
Posts whose SEO title is “Hello Ridu”:
seoTitled := query.Equal("seo.title", "Hello Ridu")const seoTitled: PostsWhere = {
'seo.title': { equals: 'Hello Ridu' }
};where={"seo.title":{"equals":"Hello Ridu"}}A path through an array uses the array’s name. A path through a blocks field adds the block’s slug,
so a note block’s text inside a card block inside a layout field is
layout.card.children.note.text. Row positions never appear in a path.
A path through arrays or blocks reaches the field in every row, so a document can match on any row:
| Comparison | Matches when |
|---|---|
equals, in, contains, like, ranges, exists: true |
at least one row’s value matches |
notEquals, exists: false, not |
no row’s value matches the opposite comparison |
equals: null |
a row’s value is null, or no row has the field |
Each condition in an and is matched on its own, so two conditions can be met by two different
rows. Paths inside a rich-text or other plugin field aren’t filterable; store values you need to
search in ordinary fields.
JSON fields
A JSON field can hold any value, so a comparison applies only when the stored
value is a scalar of the same type. contains and like match a stored string, ignoring case:
{ metadata: { contains: 'plan' } } matches "Launch PLAN". An array or object never contains
text and never equals a scalar; it only counts as present for exists. Use a
text list when items should match one by one.
A path into a JSON value, such as metadata.owner, follows object keys and stops at an array.
PostgreSQL, SQLite and the in-memory store support these paths; MongoDB refuses them.
Operators
| TypeScript and REST | Go | Matches |
|---|---|---|
equals, notEquals |
query.Equal, query.NotEqual |
an exact value, or any other value |
in |
query.In |
one of several values; for lists, any item equals one |
greaterThan, greaterThanEqual |
query.GreaterThan, query.GreaterThanEqual |
a larger number, date or text |
lessThan, lessThanEqual |
query.LessThan, query.LessThanEqual |
a smaller number, date or text |
contains |
query.Contains |
text that contains the value, ignoring case |
like |
query.Like |
text that contains every word of the value, ignoring case |
exists |
query.Exists |
a field that has a value, or doesn’t |
and, or, not |
query.And, query.Or, query.Not |
combinations of other filters |
New to building filters in Go? Filters and paths explains
query.Expression and how the same filters work in access rules.
When a filter is refused
Ridu checks a filter before it reads anything. An unknown path, an operator the field doesn’t
support, or a field with a read rule fails with bad_query (400) in Go, over REST and in the SDK.
The issue code says why: unsupported_operator or unsupported_path.
{
"error": {
"code": "bad_query",
"status": 400,
"issues": [
{
"code": "unsupported_operator",
"path": "tags",
"message": "text list \"tags\" does not support \"equal\" …"
}
]
}
}Over HTTP, a where holds at most 100 comparisons, nested at most 16 deep, with at most 100 values
in one in. A path has at most 64 segments and 4,096 bytes.