~/blogs/firebase-search-map
Search in Firebase Is Still Weird, Here's the Map I Wish I Had
A practical map of Firebase search tradeoffs, from exact matches and prefix hacks to Typesense, Data Connect, and why Neon made sense.
What's good, gentle programmer.
Let's talk about one of the most annoying things about Firebase.
I've been using Firebase for about 9 years, and for all its speed, convenience, and glory, one important feature has remained frustratingly unsupported natively: true search.
If you are reading this, there is a good chance you have gone down this rabbit hole too.
It usually starts with a very innocent thought:
I just want users to search my data.
Then you discover that in Firestore, this is not nearly as straightforward as it sounds.
I've spent hours, and honestly weeks, circling this problem. It is one of those problems that keeps coming back. You build something new, your product gets a little more serious, and suddenly search matters again.
So this post is the map I wish I had. Not the polished fantasy version. The real one. The tradeoffs, the dead ends, the hacks, and the solution I eventually chose.
First, what do I mean by "true search"?
When I say true search, I mean the kind of text querying users already expect from modern products.
Not just:
- exact matches
- one rigid prefix
- one specially formatted field
I mean things like:
- partial matches
- flexible matching
- searching across meaningful text
- eventually ranking or relevance
- a search experience that actually feels like search
My use case was also broader than a simple username lookup. I needed search across events, friend names, users, places, and other social objects tied to the product experience.
Once search is powering discovery across multiple entity types, the cheap hacks start breaking down fast.
The uncomfortable truth
There is no straightforward way to do true search directly in Firestore.
Yes, there are workarounds. Yes, there are patterns. Yes, you can fake parts of it. But if you are expecting Firestore to behave like a search engine, or even like a richer query system, you are going to have a bad time.
The paths I explored
There are a few common routes people take when trying to add search to Firebase. Some are cheap. Some are clever. Some work for narrow cases. Some become painful fast.
Exact match search
path 01
This is the cheapest place to start. You query against a normalized field and look for a direct match.
This can work for usernames, handles, exact labels, and tightly controlled searchable fields.
Pros
- cheap
- simple
- no extra infrastructure
- good enough for very narrow use cases
Cons
- requires exact matching
- too limited for broader discovery
- weak user experience once users expect real search behavior
- breaks down quickly when search spans multiple entity types
const q = searchText.trim().toLowerCase();
const snapshot = await getDocs(
query(collection(db, "users"), where("usernameLower", "==", q))
);
const results = snapshot.docs.map((doc) => doc.data());Cheap and simple. Also brutally limited.
Prefix search
path 02
This is the next thing most people try. It can work reasonably well when you want a starts-with style behavior.
Pros
- still relatively cheap
- useful for narrow lookup experiences
- better than exact match when the user only knows the beginning of a term
Cons
- still not real search
- mostly okay for things like usernames
- weak for mid-word or flexible matches
- not enough for broad search across events, people, and places
- requires normalized data strategy to behave consistently
const prefix = searchText.trim().toLowerCase();
const snapshot = await getDocs(
query(
collection(db, "users"),
where("nameLower", ">=", prefix),
where("nameLower", "<=", prefix + "\uf8ff")
)
);Decent for usernames or names. Not enough for flexible multi-entity search.
Denormalizing strings or precomputing fragments
path 03
Another path is to break text into smaller pieces and store those pieces in a way Firestore can query.
This might mean storing search tokens, prefixes, normalized variants, or additional fields for matching.
Pros
- can push Firestore further than expected
- works for targeted use cases
- keeps everything in one system
Cons
- extra write complexity
- extra storage
- hard to maintain cleanly
- gets ugly as requirements grow
- still not a true search engine
{
"eventName": "Afrobeats on the Roof",
"searchTokens": ["afro", "afrobeats", "roof", "the", "on"]
}This works, but notice what happened. You are now storing extra search data everywhere.
Using array-contains and similar tricks
path 04
This is part of the broader workaround family. You preprocess data into arrays or indexed fragments, then query against those.
Pros
- can help with structured matching
- useful in some narrow query situations
- can be combined with denormalized tokens or labels
Cons
- gets messy fast
- requires storing a lot of extra data
- increases the size of each document
- can increase storage cost significantly on larger datasets
- weak for richer text search
- poor long-term foundation if search is central to your product
const token = searchText.trim().toLowerCase();
const snapshot = await getDocs(
query(collection(db, "events"), where("searchTokens", "array-contains", token))
);Good enough for narrow matching. Messy once you need richer behavior.
Sync Firestore into a dedicated search provider
path 05
This is the path Firebase itself tends to push you toward. Use an extension or sync pipeline to mirror your data into something built for search.
Pros
- better search quality
- purpose-built indexing and ranking behavior
- faster to ship than building your own infrastructure
- often comes with Firebase-friendly extensions
Cons
- another service now owns part of your data flow
- extra cost
- less control
- another dependency to reason about
- often that system exists only for search, not broader product value
This bothered me more than I expected. Not because it is inherently bad, but because I did not love pushing data into another service just to unlock one missing capability.
Typesense and other search sync extensions
path 06
We also tried the Typesense extension route. This is one of the cleaner ways to keep Firebase while outsourcing search to something purpose-built. And to be fair, it works.
Pros
- proper search behavior
- purpose-built search engine
- faster to ship than building everything from scratch
- more realistic for multi-entity search than Firestore-only hacks
Cons
- another service in the architecture
- more cost to think about
- more moving pieces
- less control over the overall shape of the system
- it felt like a database I had less visibility into
Firestore -> Firebase extension -> Typesense index -> app search query -> resultsPragmatic. Just not the architecture I wanted to own.
Firebase Data Connect
path 07
This is what sent me deeper down the rabbit hole. When Firebase Data Connect showed up, it immediately caught my attention.
It opens the door to relational-style querying inside the broader Firebase ecosystem. For the right team, this can be a strong solution, especially if you want a database setup that does more than search.
Pros
- relational model
- more expressive querying
- closer to the data access patterns many apps eventually need
- part of the Firebase world
Cons
- cost felt heavier than I wanted
- the setup felt more wrapped than expected
- the GraphQL layer added complexity
query SearchUsers($term: String!) {
users(where: { name: { _ilike: $term } }) {
id
name
username
}
}This is directionally closer to the kind of querying I wanted. But for me the setup and cost still felt heavier than I wanted.
What I realized
After going through all of this, I started thinking less about "how do I force search into Firestore?" and more about "what kind of system actually enables search well?"
Because true search is not really a Firestore trick. It is a data-modeling problem and a query problem.
And once your app grows, it often starts pushing you toward tools that are better at richer querying, indexing, and structured relationships.
Why Neon made sense for me
Neon gave me something I had been looking for for a while:
- a database that felt easier to reason about for richer querying
- a model that felt better aligned with real search needs
- serverless characteristics that felt friendlier for early-stage and cost-conscious building
- no cost when it is not being used, which matters a lot when you are still early
- pricing that still felt good as usage scales
- the possibility of using the database for more than just search
If I am going to introduce another data system, I do not want it to be a single-purpose tax if I can avoid it. I want it to unlock broader product value.
SELECT id, name, type
FROM searchable_items
WHERE name ILIKE '%afrobeats%'
ORDER BY popularity DESC
LIMIT 20;My conclusion
If your search needs are simple, Firestore hacks can be enough.
If you only need username lookup, exact match behavior, or tightly scoped filtering, then stay simple. Do not overbuild.
But if you want true search, or something closer to it, I think it is better to be honest early. Firestore is not really the right long-term tool for that problem by itself.
You can patch around it. You can extend it. You can sync it elsewhere. But at some point, the real question becomes whether your data layer should evolve.
For me, Neon was the answer.
Final thought
A lot of engineering pain comes from asking one tool to do a job it was never really designed to do.
Firestore is still great at many things. Search just is not one of its cleanest stories.
This post is my way of telling you: there are paths, there are tradeoffs, and sometimes the best solution is not another workaround. It is choosing the right system.
If you want, in a follow-up post I can break down:
- exact-match search in Firestore
- prefix strategies
- tokenization / denormalization approaches
- array-contains patterns and where they break
- Firebase extensions for external search sync
- Data Connect tradeoffs
- why Neon fit my use case better
