Skip to Content
TutorialsDeveloper Series6. Relations & Linked Data

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_id M2O field on tasks linking each task to a project
  • A tags collection 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

Relation typesM2O (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_id

This 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-m2o

See 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:

app/(authenticated)/content/tasks/[id]/page.tsx
"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 junction

As 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-m2m

See 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:

  1. Open a task at /content/tasks/<id>
  2. The Tags field should appear as a multi-select picker listing frontend, backend, urgent
  3. Select two tags and save
  4. Reload the task — the selected tags should still be shown
  5. Go back to the tasks list — if you’ve added the tags.tag_id.name field (next step), tag names appear inline in the table

Tags vs Statusstatus 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:

app/(authenticated)/content/tasks/page.tsx
// 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

RelationField typeComponentUse case
M2O (Many-to-One)Foreign key fieldselect-dropdown-m2otask → project
M2M (Many-to-Many)Junction tablelist-m2mtask ↔ tags
O2M (One-to-Many)Reverse FKlist-o2mproject → tasks (read-only sub-list)
M2A (Many-to-Any)Polymorphiclist-m2atask → 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.

Continue to Tutorial 7: File Uploads →

Last updated on