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 typebelongsTo (single ID) or hasMany (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 read permission on the target collection, the expanded field returns null (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 record
    • restrict — prevents deletion if any records reference it
    • set-null — sets the relation field to null on 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 hasMany relations on large result sets can be slow. Consider fetching the related records separately if you need more than 50 items.