Relationships, lists, and join types

When to get into a relationship, and when not to.

You create a Project and a Task. A project has tasks, so the tempting move is to add a tasks list field toProject. It is logically valid.

But that is not usually the first place the relationship should live. In Bubble, the simpler structure is usually the reverse: each Taskstores the Project it belongs to, and the project page searches for tasks where project = Current Page's Project.

Start with the child

Default to storing the parent on the child. If a task belongs to a project, the task should have a project field. If an invoice belongs to a client, the invoice should have a client field. If a comment belongs to a ticket, the comment should have a ticket field.

Project
name
status
Task
title
done?
project: Project
The child stores the relationship. The parent page searches for children that point back to it.

This keeps the relationship attached to the thing that needs it most. A task can move to another project by changing one field on the task. A search can return only the tasks for one project, then sort, filter, and paginate those results. A workflow that creates a task only has to set the task's project; it does not also have to edit the project.

It is easy to get counts of tasks for each project too. You can search for tasks where project = Current cell's Project and count the result. You do not need the project to carry a stored list just to answer that question.

A parent list is a stored snapshot. It can be useful, but it is notfree. If the project has a list of tasks, then every workflow that creates, deletes, or moves a task also has to maintain that list. A database trigger can do that work, but it adds another layer to reason about.

When to choose a list

List fields are useful when the list itself is part of the business rule. The most common case is order. A course can have a list of lessons because the order matters. A checklist can have a list of steps because the client expects to drag them into sequence. A navigation menu can have a list of links because the list is small and deliberately arranged.

You can set order as a number on the child instead of storing it in a list, but the list field tends to be the easiest way to implement manual ordering in Bubble.

Keep these lists small. As a rough rule, if you expect fewer than 50 items and the list has a specific job, a list field can be reasonable. If the list might grow into hundreds of records, use a related field on the child and search for the children instead.

Lists can also help with privacy rules. Bubble privacy rules are good at checking whether the current user is in a list on the thing being protected. A Project might have an allowed users list because the rule is simple: this user can see the project if they are in that list.

Lists are high maintenance

A list field lives on one thing. Updating it means editing that thing. If two workflows update the same list at nearly the same time, both can start from the same old value. One workflow adds Alice, the other adds Ben, and the later save can overwrite the earlier change. This is a race condition.

Wf A
read [Amy]
+Alice
A saves
Wf B
read [Amy]
+Ben
B saves
Tasks field
[Amy]
A writes
B overwrites
Both workflows read the same old list. The second save replaces the first save's version.

Lists also create cleanup work. If a task is deleted, the project'stasks list needs to remove it, particularly in larger apps where you should not rely on automatic cleanup as the only rule. If a task moves from one project to another, one list needs to lose it and another list needs to gain it. Every workflow that touches the relationship has to preserve those rules.

This is why a list should have a good reason to exist.

If you do use a list, usually keep the direct field as well. Do not only store Project's tasks. StoreTask's project too.

Course
title
lessons: list of Lessons
Lesson
title
course: Course
When a list has a job, keep the list and the direct relationship in sync.

The list can do the thing it is good at, such as preserving lesson order or allowing a privacy rule to check access. The direct field still gives each child a clear owner. It supports searches, moves, cleanup workflows, and debugging. When the only reference is the parent's list, the child does not know where it belongs easily.

Join types

Most relationships can be handled with a direct field on the child, and some small ordered or access-control relationships can use a list field. Join types are rarely needed in Bubble.

Use a join type when the relationship itself has data. ABooking Attendee can connect a Booking to aUser, but also store whether that attendee has paid, whether they attended, and when they were checked in. A Project Membercan connect a Project to a User, but also store role, invited date, accepted date, and notification settings.

Booking
date
session
Booking Attendee
booking: Booking
participant: User
paid?
attended?
User
name
email
Use a join type when the connection needs fields of its own.

The default rule is still simple: do not use a list field for a related data type unless you have a specific reason. Start with the child pointing at the parent, add a list only when the list itself has a job, and use a join type only when the relationship needs fields of its own.