How to Replace Google Places Autocomplete Without Rewriting Your Matching System
First-hand note from delivery work — written against a real shipped project constraint, not a generic tutorial outline.
- Author
- Shafin Sadnan
- Publish date
- Latest update

Table of contents (16)
- The real problem was not “replace Google Maps”
- Split location search from radius matching
- Decide what must change and what must stay stable
- the external location-search/autocomplete dependency;
- the local search data and lookup path that produces location candidates.
- the downstream geospatial matching responsibility;
- the existing MongoDB radius-query behavior;
- the product flow that depends on coordinates rather than on a specific autocomplete vendor.
- Build the local search path
- Keep the downstream coordinate contract stable
- Verify the behavior that actually matters
- What I took from the change
- map the responsibilities before choosing what to rewrite;
- preserve working logic that is outside the actual problem;
- keep downstream interfaces stable when possible;
- verify the final product behavior, not only the new component in isolation.
Key takeaway
How I replaced an external location-search dependency with a self-hosted OpenStreetMap + SQLite autocomplete path while keeping the existing geospatial matching logic intact.
Replacing a dependency is easy if you are willing to rewrite everything around it. The harder and usually more useful version is removing one dependency while leaving the parts that already work alone.
That was the important constraint in a Tutorliy location-search change. The platform had an external Google Places/location-search dependency in the relevant autocomplete flow. Underneath that search experience, however, the product already had MongoDB geospatial radius matching that handled the actual distance-based matching behavior. The full delivery boundary is documented in the Tutorliy location dependency replacement case study.
The goal was not to prove that a completely different matching architecture could be built. The goal was narrower: replace the external search dependency without destabilizing the production matching logic underneath it.
The real problem was not “replace Google Maps”
A location feature can look like one system from the UI, but it often contains multiple responsibilities.
One responsibility is search: a user types a place name and the interface returns useful location candidates with coordinates.
Another responsibility is matching: the application takes coordinates and decides which records are within a given radius.
Those responsibilities were connected, but they did not need to be replaced together. Treating them as one giant “maps system” would have made the change larger than necessary.
Split location search from radius matching
Before changing code, the useful question was: where does the external dependency actually end?
For this change, the search/autocomplete path was the replaceable layer. The MongoDB geospatial matching layer was a separate downstream responsibility that could keep doing its existing job as long as it continued receiving usable coordinates.
That distinction changed the migration plan. Instead of asking, “How do we rebuild location matching?” the question became, “How do we produce the same kind of location input without relying on the external autocomplete dependency?”
Decide what must change and what must stay stable
The safest production changes often start with a preservation list.
Change:
- the external location-search/autocomplete dependency;
- the local search data and lookup path that produces location candidates.
Preserve:
- the downstream geospatial matching responsibility;
- the existing MongoDB radius-query behavior;
- the product flow that depends on coordinates rather than on a specific autocomplete vendor.
This boundary mattered because it prevented a dependency-removal task from turning into an unnecessary rewrite of working matching logic.
Build the local search path
The replacement used Bangladesh OpenStreetMap data as the local location-data source. That data was processed into a SQLite search database using FTS5, with a local autocomplete API sitting in front of it for the application flow.
At a high level, the new path became:
Bangladesh OpenStreetMap data -> SQLite FTS5 -> local autocomplete API -> coordinates.
The important design point was not a particular library name. It was that the application could now resolve location-search candidates through a self-hosted path rather than requiring the previous external autocomplete dependency for that responsibility.
Keep the downstream coordinate contract stable
A migration becomes much safer when the system on the other side does not need to know that the provider changed.
The matching layer still needed coordinates. It did not need to care whether those coordinates originated from Google Places or from the new local search path.
That allowed the existing MongoDB geospatial radius matching to remain in place. The new work replaced the input/search layer, not the matching responsibility itself.
This is a pattern I try to preserve in production work: when an interface between two parts of a system is already useful, keep that interface stable unless changing it solves a separate real problem.
Verify the behavior that actually matters
A replacement is not complete because the new endpoint returns data. The product behavior downstream also needs to remain correct.
After deployment, the change was checked functionally and through radius behavior. That verification mattered because the risk was not only “does autocomplete return a place?” The real risk was “does the rest of the location-dependent product flow still behave after the source of those coordinates changes?”
The verification therefore needed to cover both the new search path and the preserved matching path.
What I took from the change
The strongest lesson was not about OpenStreetMap or SQLite specifically. It was about migration boundaries.
When replacing an external dependency in an existing product:
- map the responsibilities before choosing what to rewrite;
- preserve working logic that is outside the actual problem;
- keep downstream interfaces stable when possible;
- verify the final product behavior, not only the new component in isolation.
A smaller change is not automatically a better change. But when the existing system already contains a working responsibility, replacing only the constrained layer can reduce unnecessary risk and keep the engineering effort focused on the problem that actually needs solving.
If you are evaluating similar dependency-replacement work, the product engineering services page outlines how I scope fixes, migrations and rebuilds. Have a dependency or integration you need to replace without destabilizing the rest of the system? Start a project.

Working through something similar?
Send context from this note and I will reply with a practical next step.
Discuss this topicSee related case studyProduct engineering services
