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.
- client name
- text
- client email
- text
- assigned engineer name
- text
- job status
- text
- invoice amount
- number
- invoice paid
- yes/no
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.
- name
- text
- billing email
- text
- client
- Client
- assigned engineer
- User
- status
- Job Status
- job
- Job
- amount
- number
- status
- Invoice Status
- sent at
- date
- paid at
- date
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.
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.