Relational Data

Reference records across collections and fetch them in a single query using populate.

How it works

Koolbase doesn't enforce foreign keys — but you can store the ID of a related record as a field in your data. For example, a posts record might have an author_id field containing the ID of a record in the users collection.

When querying, pass a populate list to resolve those references server-side. The API fetches the related records in a single batched query and injects them inline — no extra round trips from your app.

The populated field is injected with the _id suffix removed. So author_id becomes author, and category_id becomes category.

Storing references

Store the related record's ID as a field in your data. Use the _id suffix by convention — it makes populate work automatically.

lib/posts_screen.dartDart
// Insert a post with a reference to the author
await Koolbase.db.collection('posts').insert({
  'title': 'Getting started with Koolbase',
  'body': 'Koolbase makes Flutter backends easy...',
  'author_id': 'user-uuid-here',       // reference to a users record
  'category_id': 'category-uuid-here', // reference to a categories record
});

Querying with populate

Use .populate() on the query builder. Pass a list of strings in the format "field_name:collection_name".

lib/posts_screen.dartDart
final result = await Koolbase.db
  .collection('posts')
  .populate(['author_id:users', 'category_id:categories'])
  .get();

for (final post in result.records) {
  final title = post.data['title'] as String;

  // Populated author record
  final author = post.data['author'] as Map<String, dynamic>?;
  final authorName = author?['data']['name'] as String?;

  // Populated category record
  final category = post.data['category'] as Map<String, dynamic>?;
  final categoryName = category?['data']['name'] as String?;

  print('$title by $authorName in $categoryName');
}

Populate format

Each populate string follows the pattern field_name:collection_name. The field_name is the key in your record's data that holds the referenced ID. The collection_name is the collection to look up.

Response shape

The original reference field is preserved and the populated record is injected alongside it:

{
  "id": "post-uuid",
  "data": {
    "title": "Getting started with Koolbase",
    "author_id": "user-uuid",
    "author": {
      "id": "user-uuid",
      "data": {
        "name": "Kennedy Owusu",
        "email": "kennedy@example.com"
      },
      "created_at": "2026-03-25T00:00:00Z",
      "updated_at": "2026-03-25T00:00:00Z"
    },
    "category_id": "category-uuid",
    "category": {
      "id": "category-uuid",
      "data": { "name": "Engineering" },
      "created_at": "2026-03-25T00:00:00Z",
      "updated_at": "2026-03-25T00:00:00Z"
    }
  },
  "created_at": "2026-03-25T00:00:00Z",
  "updated_at": "2026-03-25T00:00:00Z"
}

Combining with filters and ordering

.populate() works alongside all other query methods:

lib/posts_screen.dartDart
final result = await Koolbase.db
  .collection('posts')
  .where('published', isEqualTo: true)
  .orderBy('created_at', descending: true)
  .limit(10)
  .populate(['author_id:users'])
  .get();

Limits

Max populate fields per queryNo hard limit — each field runs one batched query
Max referenced IDs per field100 (capped server-side)
Nested populateNot supported — only one level deep
Access rulesThe populated collection's read rule is not enforced — populate is a server-side join