I had a search problem in Bubble that I already knew how NOT to solve, Advanced Filters had already cost me heavily on workload units in a previous app. So this time I tried something different. and I actually got the idea from Bubble’s AI Agent.
Here’s what I needed to achieve:
I have multiple fields in my User data type. Some are text fields, while others are lists of texts. I wanted to build a search filter that could search across all of them.
I didn’t want to use (Advanced Filter) because I had already used that approach in one of my previous apps. It increased my workload units and the app became noticeably slower.
So here’s the approach I came up with.
Bubble’s contains keyword(s) constraint only works on text fields, not on lists of texts. Because of that, I created a new text field called Hidden Search Tag.
Whenever a user saves their profile, I combine all the values that should be searchable into this hidden field. Now all my searchable data exists in a single text field, and I can simply use the contains keyword(s) constraint. So far, it has been working really well.
Then I found another issue.
As far as I know, contains keyword(s) only checks the first 250 words. My searchable content could eventually grow beyond that, so I wanted to make the solution a little safer.
To handle this, I created another field called Hidden Search Tag 2 and stored the remaining searchable text there. Then I used merged with in the repeating group to combine the results from both searches.
That’s how I solved the problem.
I’m not saying this is the best or the correct approach. It’s simply the solution I came up with, and it seems to be working well for my use case.
If you know a better or more efficient way to achieve this in Bubble without relying on Advanced Filters, I’d genuinely love to hear it. If your approach is better, I’ll happily replace mine.
Thanks!
This approach has been around since at least 2020…some things that can maybe cause issues is having some fields empty and so not all the search filters can be found reliable if your display requires any kind of item number references for the long single string that contains all the searchable text.
Some other approaches are JSON which is just text but can help avoid issues with empty filter values, or use api objects which is also basically JSON but can be used in dynamic expressions.
Hey, thanks a lot for the warning about the empty fields issue, it was a really important catch and saved me from a bigger problem down the line.
So after digging into it, here’s what we did to fix it without using JSON:
The Problem The hidden search fields were storing ,,,, because static commas were hardcoded between expressions, and empty list fields were still writing their separator into the string even when they had no data.
The Fix
- Removed all static commas between expressions and replaced them with a space, so empty fields leave no gap in the string
- Added
:filtered [Current option's name is not empty] on every list field before :each item's name so if a user has no Primary Skills or Secondary Skills saved, that section contributes nothing to the string at all
- Used
:merged with to combine multiple list fields first, then applied :each item's name once at the very end - keeping everything in list mode until the last step
The Result Instead of storing ,,Sales,,React,,, we now get clean strings like Sales React with no empty gaps - which makes the contains keyword(s) search reliable and consistent.
No JSON needed, just clean Bubble expressions. Thanks again for pointing it out!
I currently use this method, also with spaces. I call it “quick search” field. It’s working well.
My only concern is that as the app grows I am not sure if this is an indexed field, so it may get sluggish at scale. Maybe someone can give a definitive answer on this.
But for now it’s an ok solution.
I’m not saying that what I do is the best way to do it but I personally hate having to do a second field. What I do is I grab all the fields I want to be searchable, put it all in one text field. When it gets anywhere near the 200, I just have a quick API call to condense it to only important terms and words and then store that. It just makes it simpler, cleaner, way faster searches, and any important keyword is always going to find the records. I do this on 75-page legal documents as well and it’s just a charm at finding a document in a second. I also add in the display name of all option sets that I want to be searched and other linked client name, address, etc.
This isn’t actually the case.
Both contains and contains keyword(s) search the entire text field, regardless of its length (it’s easy to verify this with a simple test by putting the search term hundreds or even thousands of characters into the field).
You’re probably thinking about the 256-character limit relating to indexing, not searching.
Bubble only indexes the first 256 characters of a text field, so anything beyond that can’t benefit from the index. You can see the effect of this with operations like sorting, where records that only differ after the first 256 characters won’t sort correctly.
But you can search (and find) words/text way beyond those 256 characters (but anything beyond the first 256 won’t use the index to do so).
In practice, though, this is largely a moot point here because contains/contains keyword(s) almost certainly don’t use indexes anyway when searching. So splitting the text into multiple fields is unlikely to have any effect aside from making the search more complex.
They use GIN indexes behind the scenes
I’ve heard this but never seen it documented anywhere, do you have a link or know where it came from?
Interesting point…
From what I can tell, Bubble uses GIN indexes as standard on the built-in All column, which is used for Any field contains.
For other text fields, it seems to create GIN indexes when certain search constraints are used often enough to warrant it (as it does with other indexes). So yes, if contains/contains keywords are used frequently enough, Bubble appears to create a GIN index for those columns.
Whether that index actually gets used depends on the query itself and whether PostgreSQL’s query planner decides it’s beneficial.
But either way, that’s even less reason to split a search_field into multiple fields.
The 256 Character limit on indexes is mentioned here:
You can also see the limit in practice when examining the index details for a particular text column:
