Structuring your Bubble database correctly from the start prevents performance issues, simplifies workflows, and reduces workload unit consumption. This tutorial covers database design principles adapted for Bubble's architecture, including when to normalize versus denormalize, how to model one-to-many and many-to-many relationships, when to use Option Sets instead of Data Types, and common schema patterns for SaaS apps and marketplaces.
Overview: Structuring the Database in Bubble
This tutorial teaches you database design best practices specifically for Bubble. Unlike traditional SQL databases, Bubble's database has unique characteristics — real-time search updates, workload unit costs per query, and field-level privacy rules. You will learn patterns that work with these characteristics rather than against them.
Prerequisites
- A Bubble app (new or existing)
- Basic understanding of what Data Types and fields are in Bubble
- A concept of the app you want to build (even a rough idea)
Step-by-step guide
Understand Bubble's database fundamentals
Understand Bubble's database fundamentals
Bubble's database runs on PostgreSQL. A Data Type is a table, a Thing is a row, and a Field is a column. Every record automatically gets: Unique ID (32-character, immutable), Created Date, Modified Date, and Slug. The User Data Type is auto-created with email and password handling. You can have up to 1,000 custom Data Types. Key differences from traditional databases: searches auto-update in real-time on page elements, text search indexes only the first 256 characters, and the database processes approximately 100 rows per second. Deleting a field leaves the underlying data in the database.
Expected result: You understand Bubble's database architecture and its unique characteristics that affect schema design.
Design relationships between Data Types
Design relationships between Data Types
In Bubble, relationships are created by adding a field of another Data Type. One-to-many: add a field of the parent type on the child. Example: a Task has a project field (type: Project) — each task belongs to one project, but a project has many tasks. To find all tasks for a project, search Tasks where project = the specific Project. Many-to-many: add a list field. Example: a Course has a students field (list of Users) or a Student has an enrolled_courses field (list of Courses). For large many-to-many relationships, create a junction Data Type (Enrollment with student and course fields) to avoid list size limits and enable extra metadata.
Pro tip: Bubble list fields have a practical limit of about 10,000 items. For relationships that could exceed this, always use a junction Data Type instead of a list field.
Expected result: You can model one-to-many and many-to-many relationships using Bubble's field types and junction Data Types.
Decide when to denormalize for performance
Decide when to denormalize for performance
In traditional databases, normalization avoids data duplication. In Bubble, selective denormalization improves performance dramatically. Example: instead of searching Reviews and calculating average_rating on every profile page load (expensive), store average_rating as a field on the User record and update it when a new review is created. Other denormalization candidates: item counts (store comment_count on Post instead of counting with a search), computed totals (store order_total instead of summing line items), and latest values (store last_activity_date instead of searching for the most recent event).
Expected result: You know when to store computed values as fields to avoid expensive searches.
Choose between Data Types and Option Sets
Choose between Data Types and Option Sets
Option Sets are static, developer-managed lists stored as part of your app's code — they load instantly with zero workload unit cost. Data Types are dynamic database tables with privacy rules, user-generated content, and search capabilities. Use Option Sets for: statuses (Active, Inactive, Pending), categories (Electronics, Clothing, Food), roles (Admin, Member, Viewer), countries, colors, and any list that does not change per user. Use Data Types for: user-generated content, records that need privacy rules, data that users create/edit/delete, and anything requiring relationships to other database records.
Expected result: You can identify which data belongs in Option Sets versus Data Types for optimal performance.
Apply common schema patterns
Apply common schema patterns
SaaS pattern: User → Organization (many-to-many via Membership junction with role field) → Project → Task. Store the user's current_organization on User for quick access. Marketplace pattern: User with role (Buyer/Seller), Product → Order → OrderItem (junction between Order and Product with quantity and price). Social pattern: User → Post → Comment, Following (junction between two Users: follower and followed), Like (junction between User and Post). For all patterns, add created_by (User) and created_date fields on every Data Type for audit tracking.
Pro tip: Draw your schema on paper before building. Each box is a Data Type, each line is a field reference. This 5-minute exercise saves hours of restructuring later.
Expected result: You have a well-structured database schema following proven patterns for your app type.
Complete working example
1DATABASE DESIGN PATTERNS FOR BUBBLE2=====================================34RULES OF THUMB:5 1. Option Sets for static lists (zero WU cost)6 2. Denormalize computed values (counts, averages, totals)7 3. Junction Data Types for large many-to-many relationships8 4. created_by and created_date on every Data Type9 5. Maximum 1,000 custom Data Types per app10 6. List fields: practical limit ~10,000 items1112SAAS PATTERN:13 User:14 - current_organization: Organization15 Organization:16 - name, logo, plan (Option Set)17 Membership (junction):18 - user: User, organization: Organization19 - role: Option Set (Admin/Member/Viewer)20 Project:21 - organization: Organization, name, status (Option Set)22 Task:23 - project: Project, title, assigned_to: User24 - status: Option Set, due_date, completed: yes/no2526MARKETPLACE PATTERN:27 User:28 - role: Option Set (Buyer/Seller)29 Product:30 - seller: User, title, price, category (Option Set)31 Order:32 - buyer: User, total: number, status: Option Set33 OrderItem (junction):34 - order: Order, product: Product35 - quantity: number, unit_price: number3637SOCIAL PATTERN:38 User:39 - followers_count: number, following_count: number40 Post:41 - author: User, content, like_count: number42 Comment:43 - post: Post, author: User, body44 Following (junction):45 - follower: User, followed: User46 Like (junction):47 - user: User, post: Post4849DENORMALIZATION EXAMPLES:50 Instead of: Search Reviews :average rating (every page load)51 Store: User's average_rating field (update on new review)5253 Instead of: Search Comments :count (every post display)54 Store: Post's comment_count field (increment on new comment)Common mistakes when structuring the database in Bubble
Why it's a problem: Using Data Types for static lists like statuses and categories
How to avoid: Convert static Data Types to Option Sets. Use Data Types only for dynamic, user-generated, or privacy-controlled data.
Why it's a problem: Using list fields for large many-to-many relationships
How to avoid: Create a junction Data Type for many-to-many relationships that could grow large (e.g., Enrollment instead of Course's students list)
Why it's a problem: Calculating aggregates like counts and averages on every page load
How to avoid: Store computed values as denormalized fields and update them when the source data changes
Best practices
- Use Option Sets for any data that is static and developer-managed
- Denormalize computed values like counts, averages, and totals as fields
- Use junction Data Types instead of list fields for large many-to-many relationships
- Add created_by (User) and created_date fields to every Data Type for audit tracking
- Draw your schema on paper before building in Bubble
- Limit list fields to relationships that will stay under a few hundred items
- Use the 'Result of step X' operator to reference newly created records in workflows
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building a [describe your app] in Bubble.io. Help me design the database schema. What Data Types, fields, and relationships should I create? When should I use Option Sets vs Data Types? Where should I denormalize for performance?
Create a SaaS database schema with Users who belong to Organizations through a Membership junction table. Add Project and Task Data Types linked to the Organization. Use Option Sets for statuses and roles. Include denormalized fields for task counts and completion percentages.
Frequently asked questions
How many Data Types can I have in a Bubble app?
Up to 1,000 custom Data Types. Most apps need 10-30 Data Types. If you are approaching this limit, consider consolidating similar types.
Should I normalize or denormalize my Bubble database?
Use selective denormalization. Keep the core structure normalized for data integrity, but store computed values (counts, averages, totals) as fields to avoid expensive searches on every page load.
What is the difference between a list field and a junction Data Type?
A list field stores references directly on the record (simpler but limited to ~10K items). A junction Data Type is a separate record linking two types (scalable and allows extra metadata like role or date).
Can I change my database structure after building the app?
Yes, but with caution. Adding fields is easy. Deleting fields leaves orphan data. Restructuring relationships requires updating all references in workflows, pages, and privacy rules.
Can RapidDev help design my Bubble database schema?
Yes. RapidDev can design optimized database schemas for any app type, including proper relationships, denormalization strategy, Option Set planning, and privacy rule architecture.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation