Browse all documentation

Selecting and populating

Return only the fields you need, and include related documents such as a post’s author in the same read.

A read returns every field the caller may read, and relationships as document IDs. Select fewer fields, or populate relationships to get the related documents in the same request. Examples use the example schema.

Select fields

Only each post’s title and author:

Go
page, err := local.List(ctx, "posts", ridu.ListOptions{
  Select: []query.Path{
    query.Field("title"),
    query.Field("author"),
  },
  Actor: user,
})
TypeScript
const page = await ridu.list('posts', {
  select: { title: true, author: true }
});
REST
curl -G "$RIDU/api/collections/posts" \
  -H "Authorization: Session $TOKEN" \
  --data-urlencode 'select={"title":true,"author":true}'
Result: one document
{
  "id": "posts_49d8d5f5eebb60b11c3fd9f9",
  "title": "Hello, Ridu",
  "author": "users_c15d8cdad9e97e3c8ba53543",
  "createdAt": "2026-10-06T13:38:37.505562Z",
  "updatedAt": "2026-10-06T13:38:37.505562Z"
}

id, createdAt and updatedAt are always included. A selected relationship stays an ID until you populate it. Select top-level fields; REST accepts at most 256.

A selection only shapes the result. Read hooks and field read rules still receive the whole document, so a rule that checks another field gives the same answer whichever fields you select.

In TypeScript, a literal select narrows the result’s type to the selected fields. If you build the options separately, keep them literal with as const or satisfies, or the type stays broad.

Populate a relationship

Each post with its author’s name:

Go
page, err := local.List(ctx, "posts", ridu.ListOptions{
  Populate: []query.Population{{
    Path:   query.Field("author"),
    Select: []query.Path{query.Field("name")},
  }},
  Actor: user,
})
TypeScript
const page = await ridu.list('posts', {
  populate: { author: { select: { name: true } } }
});
REST
curl -G "$RIDU/api/collections/posts" \
  -H "Authorization: Session $TOKEN" \
  --data-urlencode 'populate={"author":{"select":{"name":true}}}'
Result: one document, trimmed
{
  "id": "posts_49d8d5f5eebb60b11c3fd9f9",
  "title": "Hello, Ridu",
  "author": {
    "id": "users_c15d8cdad9e97e3c8ba53543",
    "name": "Ada Lovelace",
    "createdAt": "2026-10-06T13:38:22.661404Z",
    "updatedAt": "2026-10-06T13:38:22.661404Z"
  }
}

Leave out select to get the whole related document. Ridu reads each related document with the caller’s own access, so a document they can’t read stays out, and fields they can’t read are removed. As with a top-level select, the related collection’s field read rules still see the whole document. In Go, a populated relationship’s value is a store.Populated document.

In TypeScript, a literal populate changes the relationship’s type from an ID to the related document, including the fields you selected.

Go deeper

Populate a relationship of the related document with depth, up to 5 levels:

ts
const page = await ridu.list('posts', {
  populate: { author: { depth: 2 } }
});

In Go, set Depth on the query.Population. A relationship inside a group, array or block uses its dotted path, such as 'sections.quote.source': true.

Populate every relationship

depth on its own populates every top-level relationship to that many levels:

TypeScript
const page = await ridu.list('posts', { depth: 1 });
REST
curl -G "$RIDU/api/collections/posts" \
  -H "Authorization: Session $TOKEN" \
  --data-urlencode 'depth=1'

Use it for quick reads. For production queries, name the relationships with populate: the result’s shape and cost stay predictable, and TypeScript knows the populated types. You can’t combine depth with populate. In Go, list each relationship as a query.Population.

Limits

One request can name at most 64 relationship paths and populate to depth 5. Across the whole request, Ridu expands at most 256 schema paths and reads at most 4,096 related documents. These are limits for the request, not for each document.