Slow database queries are the most common performance bottleneck in Bubble apps. This tutorial covers how to speed up Do a Search For operations by using database constraints instead of client-side filters, paginating repeating groups, caching frequently used search results in custom states, and eliminating nested searches that multiply query costs.
Overview: Database Query Optimization in Bubble
This tutorial is for Bubble builders experiencing slow page loads or sluggish repeating groups. You will learn why Bubble apps slow down as data grows and the specific techniques to fix query performance. The focus is on actionable changes you can make in the visual editor without plugins or custom code.
Prerequisites
- A Bubble app with at least one Data Type containing 100+ records
- Basic understanding of Do a Search For and Repeating Groups
- Access to your Bubble app's Logs tab for performance monitoring
- Familiarity with Bubble's Workflow and Design tabs
Step-by-step guide
Identify slow queries using the Logs tab and Debugger
Identify slow queries using the Logs tab and Debugger
Open your Bubble editor and click the Logs tab in the left sidebar. Look at the Server logs section — each entry shows the workflow or page load action, the time it took, and the workload units consumed. Sort by duration to find the slowest operations. Next, preview your app and activate the Debugger by clicking the bug icon in the bottom-right. Step through page loads and workflow actions to see which searches take the longest. Also check Settings → App Metrics to see which pages and actions consume the most WUs over time. Write down the top 3-5 slowest searches to prioritize your optimization work.
Pro tip: Server log retention varies by plan — Free gives only 6 hours. Check logs regularly or upgrade for longer retention.
Expected result: You have a list of your slowest database queries with their execution times and WU costs.
Replace client-side filters with database constraints
Replace client-side filters with database constraints
Find any Do a Search For that uses the :filtered operator after the search. This operator downloads all matching records to the browser first, then filters them client-side — extremely slow for large datasets. Move those filter conditions into the search constraints instead. Click on your Do a Search For, and in the constraints area, add conditions that match what your :filtered was checking. For example, change 'Do a Search for Products:filtered (Category = Shoes)' to 'Do a Search for Products' with a constraint 'Category = Shoes'. Database constraints run on the server and use indexes, making them dramatically faster.
Pro tip: Bubble's text search constraints only index the first 256 characters. For longer text searches, create a separate short text field optimized for searching.
Expected result: Searches that previously used :filtered now use database constraints, resulting in faster query times and lower WU consumption.
Eliminate nested searches inside repeating groups
Eliminate nested searches inside repeating groups
Open your Design tab and check your Repeating Groups. Click on elements inside each cell and look for any that have their own Do a Search For as a data source. A search inside a repeating group cell runs once per visible row — if you show 20 items and each cell has a search, that is 20 separate database queries on every page load. Fix this by pre-computing the data. For example, if each Product cell searches for its review count, add a 'review_count' number field to the Product Data Type. Use a backend workflow to update this count whenever a review is created or deleted. Then display the field directly instead of running a search.
Pro tip: Use the parent group's data reference instead of a separate search. If a cell's parent group is type Product, use Parent group's Product's field directly.
Expected result: Repeating group cells display data from fields on the parent record rather than running individual searches, reducing queries from N to 1.
Paginate repeating groups to limit data loading
Paginate repeating groups to limit data loading
Select your Repeating Group element and check its layout. If it shows all records at once with no fixed number of rows, it forces Bubble to load every matching record. Change the Repeating Group to a fixed number of rows — 10 to 20 is optimal. Add pagination controls below the group: a Next button and a Previous button. Create a custom state on the page called 'current_page' of type number with a default of 1. Set the Repeating Group's data source to Do a Search for your type with items from calculated offset. The Next button increments current_page and Previous decrements it.
Expected result: The repeating group loads only 10-20 records at a time, with working Next and Previous pagination buttons.
Cache search results in custom states
Cache search results in custom states
If the same search is used by multiple elements on a page, running it multiple times wastes WUs. Instead, store the result once. Create a custom state on your page element: click the page in the Element Tree, then click the 'i' icon to add a custom state. Name it 'cached_products' with type Product and check 'is a list'. In a Page is loaded workflow, add a Set State action: set page's cached_products to Do a Search for Products with your constraints. Then point your Repeating Group's data source to page's cached_products and your count text to page's cached_products:count. Now one search serves multiple elements.
Pro tip: Custom states reset on page reload. For data that changes rarely, this is efficient. For frequently changing data, direct searches may be preferable since they auto-update.
Expected result: Multiple elements share a single cached search result, reducing the total number of database queries per page load.
Use Option Sets instead of database searches for static data
Use Option Sets instead of database searches for static data
Identify any searches that retrieve data that rarely changes — categories, status labels, countries, or configuration values. These are better stored as Option Sets because they are cached as part of your app's code and require zero database queries and zero WUs. Go to the Data tab, click Option sets, and create a new Option Set. Add options and attributes for any extra data like icon or display_order. Then replace your search-based dropdowns and filters with Option Set references.
Expected result: Static reference data loads instantly from Option Sets with zero database queries and zero WU cost.
Complete working example
1DATABASE QUERY OPTIMIZATION SUMMARY2=====================================34DIAGNOSTICS:5 Logs tab → Server logs → Sort by duration6 Settings → App Metrics → WU breakdown7 Debugger → Step through page loads89OPTIMIZATION 1: CONSTRAINTS OVER :filtered10 Before: Do a Search for Products:filtered (Category = X)11 After: Do a Search for Products [Category = X]12 Why: Constraints use server indexes, :filtered downloads all1314OPTIMIZATION 2: NO NESTED SEARCHES IN RG CELLS15 Before: Each cell runs Do a Search for Reviews:count16 After: Product has review_count field17 Backend workflow updates count on create/delete18 Cell shows Parent group's Product's review_count19 Why: Reduces N queries to 12021OPTIMIZATION 3: PAGINATE REPEATING GROUPS22 Repeating Group: Fixed rows = 10-2023 Custom state: page → current_page (number)24 Data source: Do a Search with offset25 Next: current_page + 126 Previous: current_page - 1 (Only when > 1)2728OPTIMIZATION 4: CACHE WITH CUSTOM STATES29 Custom state: page → cached_list (Type, list)30 Page is loaded:31 Set state = Do a Search for Type32 All elements use: page's cached_list33 Why: One search serves multiple elements3435OPTIMIZATION 5: OPTION SETS FOR STATIC DATA36 Data tab → Option sets → New Option Set37 Replace DB searches with Option Set references38 Why: Zero queries, zero WUs, cached client-side3940PERFORMANCE BENCHMARKS:41 :filtered on 1,000 records → 5-15 sec delay42 Nested search in 20-row RG → 20x query cost43 Unpaginated RG with 500+ items → memory issues44 Option Set vs DB search → 100% WU savingsCommon mistakes when optimizing Database Queries and Performance in Bubble
Why it's a problem: Using :filtered instead of search constraints for simple conditions
How to avoid: Move filter conditions into Do a Search For constraints wherever possible
Why it's a problem: Running Do a Search For inside repeating group cells
How to avoid: Pre-compute values as fields on the parent Data Type using backend workflows
Why it's a problem: Displaying all records in a repeating group without pagination
How to avoid: Set a fixed number of rows (10-20) and implement pagination using custom states
Why it's a problem: Using database types for static reference data like categories
How to avoid: Convert static, developer-managed data from Data Types to Option Sets
Best practices
- Use the Logs tab regularly to monitor which queries consume the most WUs
- Always prefer database constraints over :filtered for better performance
- Paginate repeating groups to 10-20 items rather than loading all records
- Pre-compute aggregate values as fields rather than calculating on display
- Cache frequently used search results in custom states to avoid duplicate queries
- Use Option Sets for any data that does not change per user or require Privacy Rules
- Add a search_key text field for searchable content exceeding 256 characters
- Profile performance before and after changes to verify improvements
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
My Bubble app has a repeating group showing 500+ products and it takes 10 seconds to load. Each cell displays a review count using a separate search. How do I optimize this to load in under 2 seconds?
Help me optimize my product listing page. The repeating group is slow because it loads all products at once and each cell searches for review counts. I want pagination and pre-computed review counts.
Frequently asked questions
Why is my Bubble app so slow?
The most common causes are: searches using :filtered instead of constraints, nested searches inside repeating group cells, loading too many records without pagination, and unused plugins adding overhead. Start by checking your Logs tab.
How many records can Bubble handle before slowing down?
Bubble starts showing noticeable slowdowns around 30,000-50,000 records without optimization. With proper constraints, pagination, and pre-computed fields, apps can handle hundreds of thousands of records.
Do constraints cost fewer WUs than :filtered?
Yes. Constraints run server-side using database indexes and typically cost 0.2-1 WU. The :filtered operator downloads all records first then filters client-side, which is both slower and more expensive.
Should I always use custom states to cache searches?
Not always. Direct searches auto-update in real-time when data changes. Cached custom states do not auto-update. Use caching for data that does not change frequently or where the same search is referenced by multiple elements.
What is the maximum number of items for a repeating group?
For best performance, show 10-20 items per page with pagination. Bubble does not virtualize lists, so every item is rendered in the DOM regardless of visibility.
Can RapidDev help optimize my Bubble app's performance?
Yes. RapidDev specializes in Bubble performance optimization including database restructuring, query optimization, and workload unit reduction for apps experiencing slowdowns.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation