Relations
6 relation types with automatic join tables and query population.
Relations link content types together. Define them with field_type = "relation" and a relation config.
Relation Types
| Type | Description | FK Location | Data Format |
|---|---|---|---|
one_to_one | Single related record | Source table | String (target ID) |
one_to_many | Multiple records in target | Target table | Array of IDs |
many_to_one | Belongs to | Source table | String (target ID) |
many_to_many | Via junction table | Junction table | Array of IDs |
one_way | Unidirectional reference | Source table | String (target ID) |
many_way | Unidirectional many | Junction table | Array of IDs |
Many To One
The most common relation — "belongs to". The foreign key lives in the source table.
# Each course belongs to one instructor
[[fields]]
name = "instructor"
field_type = "relation"
relation = { relation_type = "many_to_one", target = "instructors" }This adds an instructor_id column to the courses table.
# Create a course linked to an instructor
curl -X POST http://localhost:9898/api/v1/cms/courses \
-d '{"title":"Rust 101","instructor":"<instructor_id>"}'One To Many
"inverse of many_to_one". The foreign key lives in the target table.
# An instructor has many courses (FK is in courses table)
[[fields]]
name = "courses"
field_type = "relation"
relation = { relation_type = "one_to_many", target = "courses" }This does not add a column — it reads the existing FK on the target side. Use it for reverse lookups.
Many To Many
Links via an auto-created junction table.
# Courses have many tags
[[fields]]
name = "tags"
field_type = "relation"
relation = { relation_type = "many_to_many", target = "tags", through = "courses_tags" }The system auto-creates the courses_tags junction table with columns course_id and tag_id. Duplicates are prevented with INSERT IGNORE.
# Create a course with tags
curl -X POST http://localhost:9898/api/v1/cms/courses \
-d '{"title":"Rust 101","tags":["<tag_id_1>","<tag_id_2>"]}'One To One
A single related record — FK in the source table.
# Each user has one profile
[[fields]]
name = "profile"
field_type = "relation"
relation = { relation_type = "one_to_one", target = "profiles" }One Way
A unidirectional reference — same as many_to_one but no reverse relation is created on the target side.
# Bookmark references an article, but the article doesn't know about bookmarks
[[fields]]
name = "article_ref"
field_type = "relation"
relation = { relation_type = "one_way", target = "articles" }Many Way
A unidirectional many relation via junction table — no back-reference on the target.
[[fields]]
name = "recommended"
field_type = "relation"
relation = { relation_type = "many_way", target = "courses", through = "course_recommendations" }Relation Config Options
[[fields]]
name = "author"
field_type = "relation"
relation = { relation_type = "many_to_one", target = "users", foreign_key = "author_id" }
required = true| Option | Required | Default | Description |
|---|---|---|---|
relation_type | yes | — | One of the 6 types above |
target | yes | — | Target table name |
foreign_key | no | {field_name}_id | Custom FK column name |
through | no | {source}_{target} | Junction table name (M2M / ManyWay) |
Additionally, required = true makes the FK column NOT NULL.
Populate Relations with include
By default, relation fields return IDs. Use the include query parameter to expand them into full records:
# Populate single relation
curl http://localhost:9898/api/v1/cms/courses?include=instructor
# Populate multiple relations
curl http://localhost:9898/api/v1/cms/courses?include=instructor,tags
# Nested populate — include lessons inside courses
curl http://localhost:9898/api/v1/cms/courses?include=instructor,lessons,tagsResponse with include:
{
"id": "abc123",
"title": "Rust 101",
"instructor": {
"id": "xyz789",
"name": "Alice"
},
"tags": [
{ "id": "t1", "name": "Rust" },
{ "id": "t2", "name": "Programming" }
]
}Without include, the same request returns:
{
"id": "abc123",
"title": "Rust 101",
"instructor": "xyz789",
"tags": ["t1", "t2"]
}Filter by Relation
Use {field_name}_id to filter by relation:
# All courses by a specific instructor
curl "http://localhost:9898/api/v1/cms/courses?instructor_id=xyz789"
# All lessons for a specific course
curl "http://localhost:9898/api/v1/cms/lessons?course_id=abc123"Junction Table Auto-Cleanup
When you delete a record that has ManyToMany / ManyWay relations, the junction table rows are automatically removed. No orphaned references.
Best Practices
- Define relations on both sides — e.g.,
many_to_oneon Course → Instructor andone_to_manyon Instructor → Courses. This enables bidirectionalinclude. - Let the system name junction tables — omit
throughunless you need a specific name; the default{source}_{target}convention is clear and consistent. - Use
one_way/many_waywhen the target content type should not have a reverse link (e.g., activity logs, audit references).
