Geolocation on Flutter Web with geolocator and the Browser API
Use geolocator on Flutter web: HTTPS secure contexts, browser permission prompts, watchPosition streams, and denial handling across platforms.
Published on • November 10, 2026
AI Assistant

Location features that work perfectly on Android can fail silently on Flutter web, where the browser - not the OS - owns the permission dialog. This post covers the secure-context rule, the geolocator web implementation, one-shot versus streaming positions, accuracy options, and how web denial differs from the mobile permission model.
HTTPS and the browser permission prompt
The browser Geolocation API is only available in a secure context: MDN marks the API as HTTPS-only, so http://192.168.x.x testing fails before your Dart code runs (localhost is exempt). On top of that, access is governed by the geolocation Permissions Policy, whose default allowlist is self - same-origin frames only. If your Flutter app is embedded in an iframe, the parent must both send Permissions-Policy: geolocation=(self b.example.com) and add allow="geolocation" to the iframe element, or the call is blocked outright with no prompt at all.
The prompt itself appears the first time you call getCurrentPosition() or watchPosition(), unless permission is already granted or denied. The lifetime of a grant is up to the user agent - it may be time-based, session-based, or permanent - and you can probe the current state with the Permissions API (granted, denied, or prompt) before asking, so you only interrupt the user when a prompt is actually pending.
Getting a position with geolocator on web
geolocator (14.1.2, a Flutter Favorite) is federated: geolocator_web is the endorsed web implementation and is pulled in automatically as a dependency. Add the package and request a position:
dependencies:
geolocator: ^14.1.2
import 'package:geolocator/geolocator.dart';
Future<Position> current() => Geolocator.getCurrentPosition(
locationSettings: WebSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 100,
maximumAge: const Duration(minutes: 5),
),
);
WebSettings is the platform-specific settings class for the browser; you must import geolocator_web when you reference it directly. The endorsed implementation has shipped since geolocator 6.2.0, so most apps need no extra wiring beyond flutter pub add geolocator.
getCurrentPosition versus watchPosition
Under the hood, Geolocator.getCurrentPosition() maps to the browser’s Geolocation.getCurrentPosition(), while Geolocator.getPositionStream() maps to watchPosition() with clearWatch() on cancellation. One-shot is for “center the map now”; the stream is for continuous tracking:
final sub = Geolocator.getPositionStream(
locationSettings: WebSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 50, // meters between updates
),
).listen((pos) => debugPrint('${pos.latitude}, ${pos.longitude}'));
// later
await sub.cancel();
You can call watchPosition() without a preceding getCurrentPosition() - the browser fires the callback on the first fix and again whenever the device moves or a better position arrives. The raw browser API takes the same shape:
const watchId = navigator.geolocation.watchPosition(success, error, {
enableHighAccuracy: true,
maximumAge: 30000,
timeout: 27000,
});
navigator.geolocation.clearWatch(watchId);
Accuracy, timeouts, and caching
By default the browser tries to answer as fast as possible with a low-accuracy result (Wi-Fi or IP positioning), because a real GPS fix can take a minute or more. enableHighAccuracy: true trades battery and latency for precision; timeout caps how long the browser attempts the fix before failing with TIMEOUT; maximumAge lets a cached fix up to that age be reused instead of querying the device. In geolocator these surface as WebSettings(accuracy: ..., maximumAge: ...) plus a timeLimit on getCurrentPosition(), which throws a TimeoutException when exceeded. Note that MDN warns the vanilla API may be unavailable in China, where Wi-Fi positioning is typically served by local providers instead of Google.
The Android and iOS permission models versus web
On mobile, geolocator mirrors OS-level consent: Geolocator.checkPermission() and requestPermission() return denied, deniedForever, whileInUse, or always, and deniedForever means the user must be sent to settings via Geolocator.openAppSettings() (or openLocationSettings() for the location toggle). Android can only report whileInUse/always/denied when checking - permanent denial is detectable only as a side effect of requestPermission() - and Android 12+ users may grant approximate-only access, which Geolocator.getLocationAccuracy() reports as LocationAccuracyStatus.reduced. iOS separates whileInUse from always, the latter requiring the location entry in UIBackgroundModes on iOS 16+.
Web has none of that structure: there is no app settings screen, no whileInUse versus always, and no openAppSettings() to fall back on. The docs note that checkPermission() returns denied whenever the browser lacks the JavaScript Permissions API, even though getCurrentPosition() and getPositionStream() still work - so treat the web permission result as advisory and let the real call surface the outcome.
Handling denial and reaching for package:web
The browser reports failures through GeolocationPositionError codes that geolocator converts into exceptions: 1 PERMISSION_DENIED (user said no, or Permissions Policy blocked it), 2 POSITION_UNAVAILABLE (an internal source failed), and 3 TIMEOUT. Map them to distinct UI rather than one generic error:
try {
final pos = await Geolocator.getCurrentPosition(
locationSettings: const WebSettings(accuracy: LocationAccuracy.medium),
);
} on TimeoutException {
showSnack('Location is taking too long - try again.');
} catch (e) {
// denied, unavailable, or service disabled
showSnack('We could not get your location. You can still browse.');
}
Also check Geolocator.isLocationServiceEnabled() before promising a fix, and offer a manual address fallback when denial happens - browsers let users re-prompt only after they reset the site permission. Reach for package:web directly when you need the raw API: feature-detect with "geolocation" in navigator, read position.coords plus altitude/speed/heading, or query navigator.permissions.query({name: 'geolocation'}) to decide whether to show an explanation first. For everything else - streams, settings, and cross-platform code paths - geolocator’s single API is the better abstraction.
Takeaway: Serve your Flutter web app over HTTPS, ask for location from a user gesture, distinguish getCurrentPosition one-shots from watchPosition streams with explicit accuracy and timeout options, and handle web’s flatter permission model by mapping browser error codes to graceful fallbacks instead of expecting deniedForever-style settings redirects.
Sources
- https://pub.dev/packages/geolocator
- https://pub.dev/packages/geolocator_web
- https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API
- https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API/Using_the_Geolocation_API
- https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError
- https://docs.flutter.dev/