Tutorial 6: Relations & Linked Data
Real applications have data that references other data. In the project management app, tasks belong to projects (Many-to-One) and can have multiple tags (Many-to-Many). DaaS handles these relationships at the API level — and Buildpad UI provides ready-built components to edit and display them.
By the end you’ll have:
- A
project_idM2O field ontaskslinking each task to a project - A
tagscollection with a M2M junction table connecting tasks to tags - A project picker auto-rendered on the task form via
select-dropdown-m2o - A tags editor auto-rendered on the task form via
list-m2m - Deep-filtered queries to show tasks by project
You don’t wire interfaces by hand. CollectionForm reads field metadata from DaaS and picks the right interface automatically. As long as the FK exists and the matching component is installed, the dropdown just appears — no fieldOverrides prop, no manual config. The steps below are about setting up the data (FK + junction table) and the components (CLI install). The form does the rest.
Prerequisites
- Completed Tutorial 5: Workflow Automation.
- The
projectsandtaskscollections exist with seed data.
Relation types — M2O (Many-to-One): many tasks belong to one project. M2M (Many-to-Many): a task can have many tags, and a tag can apply to many tasks, via a junction table. O2M (One-to-Many): the reverse of M2O — a project has many tasks.
Add the M2O Relation: Tasks → Projects
Ask your AI assistant to add the relation:
/create-collection add M2O relation from tasks to projects on field project_idThis is a free-form instruction the model interprets. The documented field-type shortcut for the same thing when creating a collection is project_id:m2o:projects.
The skill uses MCP to register project_id as an M2O field in DaaS automatically, and also generates a local SQL migration file.
After this, both DaaS (already updated via MCP) and your local Postgres have the FK column — enabling deep filtering, eager loading, and automatic interface detection on the form.
Verify the M2O Component Is Installed
The project-starter ships with select-dropdown-m2o pre-installed. Confirm it appears in buildpad.json under installedComponents. If it isn’t there (e.g. in a custom setup), install it:
npx @buildpad/cli@latest add select-dropdown-m2oSee the Project Selector on the Task Form
No code change required. CollectionForm reads field metadata from DaaS, sees that project_id is an M2O relation, and renders select-dropdown-m2o automatically. Open any task at /content/tasks/<id> and the new “Project” dropdown will be there.
For reference, the existing detail page in the starter looks like this — note there is no fieldOverrides prop:
"use client";
import { use } from "react";
import { useRouter } from "next/navigation";
import { Button, Group } from "@mantine/core";
import { CollectionForm, WorkflowButton } from "@/components/ui";
import { isNewItem } from "@/lib/utils";
export default function TaskDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = use(params);
const router = useRouter();
return (
<div>
<Group mb="md" justify="space-between">
<Button onClick={() => router.push("/content/tasks")}>Tasks</Button>
{!isNewItem(id) && <WorkflowButton collection="tasks" itemId={id} />}
</Group>
<CollectionForm
collection="tasks"
id={isNewItem(id) ? undefined : id}
mode={isNewItem(id) ? "create" : "edit"}
onSuccess={(data) => {
if (isNewItem(id) && data?.id) {
router.push(`/content/tasks/${data.id}`);
}
}}
/>
</div>
);
}Need to override the auto-detected interface? Do it in DaaS field metadata (e.g. PATCH /api/fields/tasks/project_id to set a different interface), not as a React prop. CollectionForm does not accept inline interface overrides.
Create the Tags Collection and M2M Relation
/create-collection tags with fields: name (input, required), color (color)
/create-collection add M2M relation between tasks and tags via tasks_tags junctionAs above, the documented field-type shortcut for the relation step (when defining a fresh collection) is tags:m2m:tags. The natural-language form is interpreted by the model.
The skill registers both collections and the junction in DaaS via MCP, and generates local migrations.
Verify the M2M Component Is Installed
list-m2m ships pre-installed in the project-starter. If missing, install it:
npx @buildpad/cli@latest add list-m2mSee the Tags Editor on the Task Form
Again, no code change required. DaaS exposes tags as an M2M relation (with junction tasks_tags), and CollectionForm renders list-m2m automatically. Reopen the task detail page — the tags editor appears alongside the project dropdown.
Seed Tags and Verify
The tags picker will be empty until you create some tags. Seed a few via the DaaS API:
TOKEN="<your-token>"
BASE="https://<your-project>.daas.buildpad.ai/api/items/tags"
curl -s -X POST $BASE \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"frontend","color":"#4C6EF5"}'
curl -s -X POST $BASE \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"backend","color":"#12B886"}'
curl -s -X POST $BASE \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"urgent","color":"#FA5252"}'Then verify the full flow:
- Open a task at
/content/tasks/<id> - The Tags field should appear as a multi-select picker listing
frontend,backend,urgent - Select two tags and save
- Reload the task — the selected tags should still be shown
- Go back to the tasks list — if you’ve added the
tags.tag_id.namefield (next step), tag names appear inline in the table
Tags vs Status — status is a single fixed value from a closed set (todo, in progress, done). Tags are open-ended labels you create as needed: one task can have many, one tag applies to many tasks. Use tags for cross-cutting concerns like frontend, urgent, or needs-review that don’t fit into a state machine.
Filter Tasks by Project
Update the tasks list page to filter by a selected project. DaaS supports deep filtering using dot notation:
Component Prop
// Pass a static filter (e.g. from a URL query param)
<CollectionList
collection="tasks"
fields={["title", "status", "priority", "due_date", "project_id.name"]}
filter={{ project_id: { _eq: selectedProjectId } }}
enableSearch
enableFilter
/>Show the Project Name in the Task List
Use the fields deep-load syntax to include the project name in the list:
<CollectionList
collection="tasks"
fields={["title", "status", "priority", "due_date", "project_id.name", "tags.tag_id.name"]}
enableSearch
enableFilter
/>project_id.name resolves the M2O relation and displays the project’s name inline. For M2M, traverse the junction (tags.tag_id.name) to pull a related field.
Relation Type Reference
| Relation | Field type | Component | Use case |
|---|---|---|---|
| M2O (Many-to-One) | Foreign key field | select-dropdown-m2o | task → project |
| M2M (Many-to-Many) | Junction table | list-m2m | task ↔ tags |
| O2M (One-to-Many) | Reverse FK | list-o2m | project → tasks (read-only sub-list) |
| M2A (Many-to-Any) | Polymorphic | list-m2a | task → any item type |
What’s Next
Your data model now has proper relationships. In the next tutorial you’ll add file attachments and image support to tasks.