The database is the skeleton

Why maintainable Bubble apps start with the shape of the data.

When a client asks for a small change that feels like an unreasonable amount of work for the desired output, it is often because the data structure was not designed to support that request. Part of our job as developers is to anticipate what will come in future, and plan for it.

What you see in the front-end can always be changed fairly easily, but your database structure is what grounds all of your expressions, workflows, searches, reusable elements, privacy rules, and reports.

A Bubble app can only be maintainable when its database is designed to meet the needs of the business effectively.

The database decides the work

The database decides which questions are easy to ask. If invoices are their own data type, show unpaid invoices by client is a normal search. If invoice details are fields on Job, the same request is now a workaround: search jobs, filter by invoice-looking fields, and manually decide whether invoice paid means unpaid, unsent, overdue, or not yet invoiced.

The database decides which workflows are simple. If a booking has a list of participants, adding another participant is expected. If a booking hasparticipant 1, participant 2, andparticipant 3, the workflow has to check each slot in order, write to the first empty one, and either block the fourth participant or add another field.

The database also decides where business rules live. If status is controlled by an option set, every condition can use the same vocabulary. If status is loose text, every search and workflow depends on spelling.

Screen-shaped data

Screen-shaped data copies the first page you built. If a client portal has a page called Job Details, the app gets a data type calledJob Details. If the page shows client name, engineer name, job status, and invoice amount, those become fields.

Job Details
client name
text
client email
text
assigned engineer name
text
job status
text
invoice amount
number
invoice paid
yes/no
A database structure copied from the page.

This renders the page. It also bakes in several assumptions: one client per job, one engineer per job, one invoice per job, no invoice lifecycle, no client account, no engineer permissions, no reliable status vocabulary.

Those assumptions are fine only until the client asks for something just outside them. If two engineers can work on the same job, the singleassigned engineer name field has to become a relationship. If one job can have more than one invoice, the invoice fields have to become invoice records. If a client needs to log in, client email is no longer just text on the job; it belongs to a client or user account.

Business-shaped data

Business-shaped data names the things the business actually operates. The same portal might have a Client, a Job, aUser, and an Invoice. The page can still be called Job Details, but the page is not what determines our database structure.

Client
name
text
billing email
text
Job
client
Client
assigned engineer
User
status
Job Status
Invoice
job
Job
amount
number
status
Invoice Status
sent at
date
paid at
date
A database structure shaped around business objects.

Now the app has useful objects to work with. A workflow can send an invoice, not update invoice-looking fields on a job. A dashboard can search invoices directly. A reusable invoice row can accept an Invoice instead of a Job Details thing plus several text fields.

This does not mean every noun deserves a data type. It means concepts that change independently should not be trapped in the same field group. If something has its own lifecycle, permissions, owner, reporting need, or list of workflows, that is a sign it needs its own place in the database structure.

Test the next feature

Before building a data structure, push it one feature into the future. Not ten imaginary features. One realistic next request from this kind of client. Could you add that request with a normal field, relationship, search, or workflow? Or would you need to rename fields, copy data, and write workflows that compensate for the database structure?

Imagine a simple booking app. Today, one person books one session. That first version could store participant directly onBooking. Before you build it that way, walk the request forward: group bookings need more than one participant, and attendance or payment status belongs to each participant's place in that booking.

Now
One participant can book a session.
Booking has participant: User
One seat only
Next
A company books three seats.
A list of users stores who is coming, but nothing about each seat.
Limited list
Then
Each attendee has payment and attendance.
Booking Attendee stores booking, participant, paid?, and attended?
Separate type
Extend the likely request one step at a time, then check what the database structure would need to store.

The aim is not to predict every request. The aim is to test the next likely rule against the fields you are about to create. Part of that is knowing how to anticipate what the client is likely to ask for, which we will cover later. For now, use your intuition: if the next realistic request would need slot fields, copied values, or duplicate workflows, change the database structure before you build on top of it.