Relations
Link collections using relation fields and expand related records inline in API responses.
#Relations
Relations connect records across collections using foreign key references. A relation field stores the ID of a record in another collection, and the expand parameter resolves that ID to the full record in API responses.
#How it works
When you create a relation field, you specify:
- Target collection — which collection the field points to
- Relation type —
belongsTo(single ID) orhasMany(array of IDs) - Display field — which field from the target collection to show in the dashboard
- On delete — what happens when the referenced record is deleted
// A "posts" collection with an author relation pointing to "users"
{
name: 'author',
type: 'relation',
required: true,
relationConfig: {
targetCollection: 'users',
displayField: 'name',
relationType: 'belongsTo',
onDelete: 'set-null',
},
}
#One-to-one (belongsTo)
A belongsTo relation stores a single record ID as TEXT. Each post has one author:
// Creating a post with a relation
const post = await sdk.collection('posts').create({
title: 'Getting started with EmuView',
body: '<p>This guide walks through...</p>',
status: 'published',
author: 'usr_abc123' // ID of a user record
});
Without expand, the field returns the raw ID:
{
"id": "post_001",
"title": "Getting started with EmuView",
"author": "usr_abc123"
}
With ?expand=author, the ID resolves to the full record:
{
"id": "post_001",
"title": "Getting started with EmuView",
"author": {
"id": "usr_abc123",
"name": "Jane Doe",
"email": "jane@example.com"
}
}
#One-to-many (hasMany)
A hasMany relation stores a JSON array of record IDs. Each product can belong to multiple categories:
// Field definition
{
name: 'tags',
type: 'relation',
relationConfig: {
targetCollection: 'tags',
displayField: 'label',
relationType: 'hasMany',
onDelete: 'set-null',
},
}
// Creating a product with multiple tags
const product = await sdk.collection('products').create({
title: 'Premium Widget',
price: 49.99,
tags: ['tag_electronics', 'tag_featured', 'tag_sale'],
});
With ?expand=tags, the array of IDs resolves to an array of records:
{
"id": "prod_xyz",
"title": "Premium Widget",
"tags": [
{ "id": "tag_electronics", "label": "Electronics" },
{ "id": "tag_featured", "label": "Featured" },
{ "id": "tag_sale", "label": "Sale" }
]
}
#Expand syntax
#Single relation
// SDK
const posts = await sdk.collection('posts').list({
expand: 'author'
});
// HTTP
// GET /api/v1/collections/posts/records?expand=author
#Multiple relations
Comma-separate field names:
const posts = await sdk.collection('posts').list({
expand: 'author,category'
});
// HTTP: ?expand=author,category
#Nested relations
Use dot notation to expand relations on expanded records. If the author has an organization relation, you can expand both:
const posts = await sdk.collection('posts').list({
expand: 'author,author.organization'
});
This produces a nested structure:
{
"id": "post_001",
"title": "Getting started with EmuView",
"author": {
"id": "usr_abc123",
"name": "Jane Doe",
"organization": {
"id": "org_def456",
"name": "Acme Corp"
}
}
}
#Key rules
- Expand respects RBAC. If the user lacks
readpermission on the target collection, the expanded field returnsnull(belongsTo) or[](hasMany). The request does not fail — the data is filtered silently. - Each expand triggers a DB query. Expanding four relations on a list of 25 records generates additional queries. Only expand what you need.
- On delete behaviour is checked when you delete a referenced record:
cascade— deletes all records that reference the deleted recordrestrict— prevents deletion if any records reference itset-null— sets the relation field tonullon referencing records
#Gotchas
- You cannot filter directly on expanded fields. Filter on the raw relation ID instead:
{ author: { _eq: 'usr_abc123' } }. - Circular expansions (e.g.,
?expand=author,author.posts,author.posts.author) are capped at a depth limit to prevent infinite loops. - Expanding
hasManyrelations on large result sets can be slow. Consider fetching the related records separately if you need more than 50 items.