Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Implement Geotagging and Location-Based Search in FlutterFlow

Add geotagging in FlutterFlow by auto-capturing device coordinates with geolocator when users create content, storing latitude/longitude and a geohash string in Firestore. For proximity search, use a Cloud Function that queries geohash prefixes to find nearby documents efficiently. Never call Google Geocoding API on every list item render — it will exhaust your API quota within hours.

What you'll learn

  • How to auto-tag user-created content with GPS coordinates using geolocator in a Custom Action
  • How to store geohash strings in Firestore to enable efficient proximity queries
  • How to use a Cloud Function to query nearby documents by geohash prefix
  • How to display geotagged results on a Google Maps widget with marker clustering
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner12 min read40-55 minFlutterFlow Free+ (Google Maps API key required)March 2026RapidDev Engineering Team
TL;DR

Add geotagging in FlutterFlow by auto-capturing device coordinates with geolocator when users create content, storing latitude/longitude and a geohash string in Firestore. For proximity search, use a Cloud Function that queries geohash prefixes to find nearby documents efficiently. Never call Google Geocoding API on every list item render — it will exhaust your API quota within hours.

Building Location Awareness into a FlutterFlow App

Location-based features — auto-tagging photos, finding nearby businesses, showing 'within 5km' results — require two distinct capabilities: capturing a device's GPS coordinates and querying a database for documents near a given location. Firestore does not have native geospatial query support, so proximity search requires a technique called geohashing. A geohash is a string that encodes latitude and longitude into a compact form where geographically close points share a common prefix. By querying for documents where the geohash starts with a given prefix, you efficiently retrieve all items within a geographic region. This tutorial covers the complete flow from GPS capture to map display.

Prerequisites

  • FlutterFlow project connected to Firebase/Firestore
  • Google Maps API key with Maps SDK for Android, Maps SDK for iOS, and Places API enabled
  • geolocator package added to pubspec.yaml (FlutterFlow Pro required)
  • Location permission configured in iOS Info.plist and Android AndroidManifest.xml

Step-by-step guide

1

Capture GPS coordinates with geolocator on content creation

Create a Custom Action named getCurrentLocation that returns a Map containing latitude (double) and longitude (double). Add the geolocator: ^10.1.0 package to pubspec.yaml. In the action, call Geolocator.checkPermission() — if denied, call Geolocator.requestPermission(). Then call Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium) and return the position coordinates. In FlutterFlow, add this Custom Action to the beginning of your content creation Action Flow (before the Firestore Create Document action). Store the returned coordinates in Page State variables currentLat and currentLng. Use LocationAccuracy.medium rather than high — medium uses cell tower triangulation which is fast and sufficient for location tagging, while high accuracy GPS takes 5-10 seconds and drains battery.

get_current_location.dart
1// Custom Action: getCurrentLocation
2// No parameters
3// Returns: String in format "lat,lng" (e.g. "51.5074,-0.1278")
4import 'package:geolocator/geolocator.dart';
5
6Future<String> getCurrentLocation() async {
7 LocationPermission permission = await Geolocator.checkPermission();
8 if (permission == LocationPermission.denied) {
9 permission = await Geolocator.requestPermission();
10 if (permission == LocationPermission.denied) return '';
11 }
12 if (permission == LocationPermission.deniedForever) return '';
13
14 final position = await Geolocator.getCurrentPosition(
15 desiredAccuracy: LocationAccuracy.medium,
16 );
17 return '${position.latitude},${position.longitude}';
18}

Expected result: When the user opens the content creation page, the action captures their current coordinates within 2-3 seconds and stores them for use in the Firestore write.

2

Store coordinates and a geohash in Firestore

Add three fields to your Firestore content collection documents: latitude (Number), longitude (Number), and geohash (String). The geohash is a string like 'gcpvj' that encodes the location. Generate it in a Custom Action using the dart_geohash package or by implementing the algorithm directly. A geohash of length 5 covers approximately a 5km x 5km area — appropriate for most location-based apps. When writing the content document, include all three fields. The latitude and longitude are used for display (showing the exact pin on a map) while the geohash is used for querying (finding documents in a geographic area efficiently without loading every document).

encode_geohash.dart
1// Custom Action: encodeGeohash
2// Parameters: latLng (String, format: "lat,lng")
3// Returns: String (geohash of length 5)
4// Simple geohash encoding — for production, use dart_geohash package
5Future<String> encodeGeohash(String latLng) async {
6 if (latLng.isEmpty) return '';
7 final parts = latLng.split(',');
8 if (parts.length != 2) return '';
9 final lat = double.tryParse(parts[0]) ?? 0.0;
10 final lng = double.tryParse(parts[1]) ?? 0.0;
11
12 // Use dart_geohash package in production:
13 // import 'package:dart_geohash/dart_geohash.dart';
14 // return GeoHasher().encode(lng, lat, precision: 5);
15
16 // Simplified placeholder — add dart_geohash to pubspec.yaml
17 return _encodeSimple(lat, lng, 5);
18}
19
20String _encodeSimple(double lat, double lng, int precision) {
21 const base32 = '0123456789bcdefghjkmnpqrstuvwxyz';
22 var minLat = -90.0, maxLat = 90.0;
23 var minLng = -180.0, maxLng = 180.0;
24 var isEven = true;
25 var bit = 0, ch = 0;
26 final result = StringBuffer();
27
28 while (result.length < precision) {
29 if (isEven) {
30 final mid = (minLng + maxLng) / 2;
31 if (lng >= mid) { ch |= (1 << (4 - bit)); minLng = mid; } else { maxLng = mid; }
32 } else {
33 final mid = (minLat + maxLat) / 2;
34 if (lat >= mid) { ch |= (1 << (4 - bit)); minLat = mid; } else { maxLat = mid; }
35 }
36 isEven = !isEven;
37 if (bit < 4) { bit++; } else { result.write(base32[ch]); bit = 0; ch = 0; }
38 }
39 return result.toString();
40}

Expected result: Content documents in Firestore include latitude, longitude, and a 5-character geohash string alongside the regular content fields.

3

Use a Cloud Function for proximity search by geohash prefix

Create an HTTP Cloud Function named searchNearby that accepts lat, lng, and radiusKm as query parameters. In the function, compute the set of geohash prefixes that cover the search radius around the given point. For a 5km radius, a 4-character prefix covers the area. Query Firestore for documents where geohash starts with each of the computed prefixes using a range query (.where('geohash', isGreaterThanOrEqualTo: prefix).where('geohash', isLessThan: prefix + '~')). Merge the results, filter by exact distance using the Haversine formula to remove false positives from the rectangular geohash grid, and return the filtered results as JSON. Call this Cloud Function from FlutterFlow using an API call action.

search_nearby.js
1// Cloud Function: searchNearby
2// Query params: lat, lng, radiusKm
3const functions = require('firebase-functions');
4const admin = require('firebase-admin');
5
6exports.searchNearby = functions.https.onRequest(async (req, res) => {
7 res.set('Access-Control-Allow-Origin', '*');
8 const { lat, lng, radiusKm = 5 } = req.query;
9 if (!lat || !lng) return res.status(400).json({ error: 'lat and lng required' });
10
11 const center = [parseFloat(lat), parseFloat(lng)];
12 const radius = parseFloat(radiusKm);
13
14 // Compute geohash prefix length based on radius
15 // 5km radius -> prefix length 4, 1km -> length 5
16 const prefixLen = radius <= 1 ? 5 : radius <= 10 ? 4 : 3;
17 const centerHash = encodeGeohash(center[0], center[1], prefixLen);
18
19 // Query Firestore for documents near the center hash
20 const db = admin.firestore();
21 const snap = await db.collection('content')
22 .where('geohash', '>=', centerHash)
23 .where('geohash', '<', centerHash + '~')
24 .limit(50)
25 .get();
26
27 // Filter by exact distance (Haversine)
28 const results = snap.docs
29 .map(d => ({ id: d.id, ...d.data() }))
30 .filter(doc => {
31 const dist = haversineKm(center[0], center[1], doc.latitude, doc.longitude);
32 return dist <= radius;
33 });
34
35 res.json({ results });
36});
37
38function haversineKm(lat1, lng1, lat2, lng2) {
39 const R = 6371;
40 const dLat = (lat2 - lat1) * Math.PI / 180;
41 const dLng = (lng2 - lng1) * Math.PI / 180;
42 const a = Math.sin(dLat/2)**2 + Math.cos(lat1*Math.PI/180)*Math.cos(lat2*Math.PI/180)*Math.sin(dLng/2)**2;
43 return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
44}

Expected result: The Cloud Function returns a JSON array of documents within the specified radius, sorted by distance, with no false positives from rectangular grid edges.

4

Add Google Places Autocomplete for address-based search

For users who want to search by address rather than their current location, add Google Places Autocomplete. In FlutterFlow, go to the Google Maps settings and enable the Places API key. Add a TextField to your search page and an API call action on text change that calls the Google Places Autocomplete API (maps.googleapis.com/maps/api/place/autocomplete/json) with the typed text as the input parameter and your API key. Display the returned predictions in a ListView below the search field. When the user taps a prediction, make a second API call to Places Details to get the exact latitude and longitude of the selected place. Then use these coordinates as the search center for your searchNearby Cloud Function call.

Expected result: Typing an address in the search field shows a dropdown of matching places. Selecting a place fetches its coordinates and triggers the proximity search centered on that address.

5

Display results on a Google Maps widget with markers

In FlutterFlow, add a Google Maps widget to your search results page. Set the widget to receive a list of map markers generated from the searchNearby results. Each result document becomes a marker with its latitude and longitude. For the marker info window, show the content title and a thumbnail image. Add a Custom Marker Icon action to differentiate content types with different coloured pins. When the map has more than 15-20 markers in view, enable marker clustering by adding the google_maps_cluster_manager package as a Custom Widget — clustering groups nearby markers into numbered circles, preventing visual clutter on dense datasets.

Expected result: Search results appear as pins on the Google Maps widget. Tapping a pin shows the content title. Nearby pins are grouped into numbered cluster markers at low zoom levels.

Complete working example

search_nearby.js
1// Cloud Function: searchNearby
2// Deploy with: firebase deploy --only functions:searchNearby
3// Requires firebase-admin and firebase-functions
4
5const functions = require('firebase-functions');
6const admin = require('firebase-admin');
7
8if (!admin.apps.length) admin.initializeApp();
9
10// Geohash encoding (simplified — use geofire-common package in production)
11function encodeGeohash(lat, lng, precision) {
12 const BASE32 = '0123456789bcdefghjkmnpqrstuvwxyz';
13 let minLat = -90, maxLat = 90, minLng = -180, maxLng = 180;
14 let isEven = true, bit = 0, ch = 0;
15 let result = '';
16 while (result.length < precision) {
17 if (isEven) {
18 const mid = (minLng + maxLng) / 2;
19 if (lng >= mid) { ch |= 1 << (4 - bit); minLng = mid; } else { maxLng = mid; }
20 } else {
21 const mid = (minLat + maxLat) / 2;
22 if (lat >= mid) { ch |= 1 << (4 - bit); minLat = mid; } else { maxLat = mid; }
23 }
24 isEven = !isEven;
25 if (bit < 4) { bit++; } else { result += BASE32[ch]; bit = 0; ch = 0; }
26 }
27 return result;
28}
29
30function haversineKm(lat1, lng1, lat2, lng2) {
31 const R = 6371;
32 const dLat = (lat2 - lat1) * Math.PI / 180;
33 const dLng = (lng2 - lng1) * Math.PI / 180;
34 const a = Math.sin(dLat / 2) ** 2 +
35 Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
36 Math.sin(dLng / 2) ** 2;
37 return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
38}
39
40exports.searchNearby = functions.https.onRequest(async (req, res) => {
41 res.set('Access-Control-Allow-Origin', '*');
42 res.set('Access-Control-Allow-Methods', 'GET');
43
44 const lat = parseFloat(req.query.lat);
45 const lng = parseFloat(req.query.lng);
46 const radiusKm = parseFloat(req.query.radiusKm || '5');
47 const collection = req.query.collection || 'content';
48
49 if (isNaN(lat) || isNaN(lng)) {
50 return res.status(400).json({ error: 'lat and lng must be valid numbers' });
51 }
52
53 // Choose prefix length based on radius
54 const prefixLen = radiusKm <= 1 ? 5 : radiusKm <= 10 ? 4 : 3;
55 const centerHash = encodeGeohash(lat, lng, prefixLen);
56
57 const db = admin.firestore();
58 const snapshot = await db.collection(collection)
59 .where('geohash', '>=', centerHash)
60 .where('geohash', '<', centerHash + '~')
61 .limit(100)
62 .get();
63
64 const results = snapshot.docs
65 .map(doc => ({ id: doc.id, ...doc.data() }))
66 .filter(doc => {
67 if (typeof doc.latitude !== 'number' || typeof doc.longitude !== 'number') return false;
68 return haversineKm(lat, lng, doc.latitude, doc.longitude) <= radiusKm;
69 })
70 .map(doc => ({
71 id: doc.id,
72 title: doc.title || '',
73 latitude: doc.latitude,
74 longitude: doc.longitude,
75 distanceKm: Math.round(haversineKm(lat, lng, doc.latitude, doc.longitude) * 10) / 10,
76 }))
77 .sort((a, b) => a.distanceKm - b.distanceKm);
78
79 res.json({ results, count: results.length });
80});

Common mistakes

Why it's a problem: Calling Google Geocoding API for reverse geocode on every list item render

How to avoid: Reverse geocode once when the user creates content and store the resulting city/neighbourhood string in Firestore. Bind the pre-stored address string directly to the list item widget instead of calling the API on every render.

Why it's a problem: Storing only latitude and longitude without a geohash in Firestore

How to avoid: Always store a geohash string alongside lat/lng when writing location-tagged documents. The geohash enables efficient range queries that reduce the candidate set to only documents in the target geographic area.

Why it's a problem: Not filtering geohash query results by exact Haversine distance

How to avoid: Always apply a Haversine distance filter after the Firestore geohash query to remove documents that fall in the rectangular grid corners but outside the actual circular search radius.

Best practices

  • Store latitude, longitude, AND geohash on every location-tagged document — all three are needed for different operations
  • Choose geohash precision based on your search radius: precision 5 for 1-5km, precision 4 for 5-20km
  • Always apply Haversine distance filtering after geohash queries to remove rectangular boundary false positives
  • Reverse geocode at write time and store the address string — never call Geocoding API on list render
  • Use LocationAccuracy.medium for content tagging and location search — GPS-level accuracy is overkill and slow
  • Cache the user's current location in App State and refresh it on page load, not on every action
  • Enable marker clustering on Google Maps when displaying more than 15 location results simultaneously

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I am building a location-based search feature in FlutterFlow using Firestore. I store latitude, longitude, and a geohash string on each document. How do I write a Firebase Cloud Function that takes a latitude, longitude, and radius in km, queries Firestore using geohash prefix matching, and filters the results by exact Haversine distance before returning them?

FlutterFlow Prompt

Create a FlutterFlow Custom Action called getCurrentLocation that uses the geolocator package to get the device's current GPS position. It should check and request location permission, get the position with LocationAccuracy.medium, and return the coordinates as a String in 'latitude,longitude' format (e.g. '51.5074,-0.1278'). Return an empty String if permission is denied.

Frequently asked questions

Do I need a paid Google Maps API plan for geotagging and proximity search?

Google Maps Platform has a free monthly credit of $200, which covers approximately 28,000 map loads or 40,000 Geocoding API calls. For most apps with under a few thousand daily active users, the free tier is sufficient. GPS coordinate capture via geolocator does not use the Google Maps API and is completely free.

What geohash precision should I use for my search radius?

As a rule of thumb: precision 6 covers roughly 1km x 0.6km (city block level), precision 5 covers 5km x 5km (neighbourhood level), precision 4 covers 40km x 20km (city level), precision 3 covers 160km x 160km (regional level). For a 5km search radius, precision 4 or 5 both work. Precision 5 will return fewer false positives but may miss documents at the edges of the radius.

Can I use Firestore's native GeoPoint type instead of storing lat/lng as separate numbers?

Yes. Firestore has a GeoPoint type that stores lat/lng as a single field. However, Firestore still cannot query by distance natively — you still need the geohash field for proximity queries. You can store both: a GeoPoint for display purposes and a geohash string for search. FlutterFlow supports GeoPoint field types in its Firestore panel.

How do I update a document's geotag if the user edits the content and is in a different location?

In your content edit Action Flow, call getCurrentLocation again to get the new coordinates and encodeGeohash to compute the new geohash. Include latitude, longitude, and geohash in the Firestore Update Document action alongside the other updated fields. The old geohash will be replaced automatically.

Is there a simpler way to do location search without writing a Cloud Function?

GeoFlutterFire2 is a Flutter package that handles geohash querying client-side, eliminating the need for a Cloud Function. Add it to pubspec.yaml (requires FlutterFlow Pro for code export) and use its query method with a GeoFirePoint center and radius. It handles the geohash prefix computation and Haversine filtering internally.

How accurate is geohash-based proximity search?

Geohash queries are accurate for finding documents within a geographic area, but the rectangular bounding box means results can include documents slightly outside your radius. The Haversine filter removes these. The core GPS coordinates are accurate to within 3-5 metres using LocationAccuracy.medium, and within 1-2 metres using LocationAccuracy.best.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.