The Geolocation API lets a web page ask the browser for the user’s latitude and longitude. It’s a small API on the surface — one method, one callback — but the parts that actually matter in production are the parts most tutorials skip: it’s been HTTPS-only since 2016, permission state can be checked before you even prompt the user, and the error callback has three distinct failure modes that each need different handling.
Here’s the minimal version:
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(position => {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
console.log(`Your location is (${latitude}, ${longitude})`);
});
} else {
console.log("Geolocation is not supported by this browser.");
}
The HTTPS Requirement (the part that breaks most often in practice)
Since around 2016, Chrome, Firefox, and every major browser have restricted navigator.geolocation to secure contexts — pages served over HTTPS, plus localhost for local development. Call getCurrentPosition() from a plain HTTP page in a modern browser and you won’t get a permission prompt at all; the success callback never fires, and depending on the browser you either get a silent no-op or an error callback with code 1 (PERMISSION_DENIED), which is misleading because the user was never even asked.
The fix has nothing to do with your JavaScript — you need a valid TLS certificate on the origin. If you’re testing locally, http://localhost is exempted as a secure context, so the bug usually only shows up once you deploy to a real HTTP-only staging environment. I’ve debugged this exact “it works on my machine” report more than once, and it’s always the same root cause: staging was plain HTTP.
Checking Permission State Before Prompting
The Permissions API lets you check whether geolocation access is already granted, denied, or unset — without triggering a prompt. This matters for UX: you can skip showing a “Share your location” button entirely if it’s already denied, or auto-request if it’s already granted from a previous session.
navigator.permissions.query({ name: "geolocation" }).then(result => {
console.log(result.state); // "granted", "denied", or "prompt"
if (result.state === "granted") {
requestLocation();
} else if (result.state === "prompt") {
showLocationButton();
} else {
showManualLocationInput(); // denied - don't bother prompting again
}
// Permission state can change while the page is open (e.g. user
// changes it in browser settings), so listen for that too.
result.onchange = () => {
console.log("Permission state changed to:", result.state);
};
});
Not every browser supports navigator.permissions for the geolocation name (older Safari versions are the usual gap), so treat this as a progressive enhancement — fall straight through to getCurrentPosition() if navigator.permissions is undefined.
Full Error Handling: All Three PositionError Cases
getCurrentPosition() takes an optional second callback for errors, and almost every tutorial skips it entirely or just logs error.message without branching on the code. The PositionError object has exactly three possible code values, and each one calls for different UI:
function handleLocationError(error) {
switch (error.code) {
case error.PERMISSION_DENIED:
// code 1 - user clicked "Block" on the permission prompt
console.error("User denied location access:", error.message);
showManualLocationInput();
break;
case error.POSITION_UNAVAILABLE:
// code 2 - location info unavailable (GPS off, no signal, etc.)
console.error("Location unavailable:", error.message);
showRetryButton();
break;
case error.TIMEOUT:
// code 3 - request took longer than the timeout option
console.error("Location request timed out:", error.message);
showRetryButton();
break;
default:
console.error("Unknown geolocation error:", error.message);
}
}
navigator.geolocation.getCurrentPosition(
position => {
console.log(`Lat: ${position.coords.latitude}, Lng: ${position.coords.longitude}`);
},
handleLocationError,
{
enableHighAccuracy: true,
timeout: 10000, // give up after 10 seconds
maximumAge: 0 // don't accept a cached position
}
);
What surprised me the first time I actually tested all three error paths deliberately: POSITION_UNAVAILABLE fires more often than you’d expect on laptops without GPS hardware that rely purely on Wi-Fi positioning — it’s not just a mobile/GPS-signal problem. And TIMEOUT only triggers if you set the timeout option at all; without it, the browser will wait indefinitely on some platforms, which means a hung request looks identical to a silently ignored one from the user’s perspective.
How the Code Works
The feature check ("geolocation" in navigator) confirms the browser exposes the API at all — this protects against very old browsers, not against permission or HTTPS issues. getCurrentPosition() is asynchronous and takes up to three arguments: a success callback receiving a GeolocationPosition, an optional error callback receiving a GeolocationPositionError, and an optional options object (enableHighAccuracy, timeout, maximumAge). The position.coords object carries latitude, longitude, accuracy (in meters), and on supporting devices, altitude, heading, and speed — most of which will be null on a typical laptop without the relevant sensors.
Sample Output
On a successful request from a secure origin with permission already granted:
Lat: 37.7749, Lng: -122.4194
If the user denies the prompt, the error callback receives a GeolocationPositionError with this shape:
User denied location access: User denied Geolocation
(The exact wording of error.message is not standardized across browsers — Chrome’s text differs slightly from Firefox’s — so always branch on error.code, never on parsing error.message.)
PositionError Codes Reference
| Code | Constant | Meaning | Typical UI response |
|---|---|---|---|
| 1 | PERMISSION_DENIED | User explicitly denied the permission prompt, or it was pre-blocked by browser settings | Fall back to manual location entry; don’t re-prompt automatically |
| 2 | POSITION_UNAVAILABLE | The device couldn’t determine a position (no GPS fix, no Wi-Fi positioning data, airplane mode, etc.) | Offer a retry button; suggest checking location services are on |
| 3 | TIMEOUT | The position wasn’t acquired within the configured timeout milliseconds | Offer a retry, possibly with a longer timeout or enableHighAccuracy: false |
FAQs
Why does getCurrentPosition() silently do nothing on my site?
The most common cause by far is that the page is served over plain HTTP rather than HTTPS. Open your browser’s developer console — most browsers log an explicit warning like “getCurrentPosition() and watchPosition() no longer work on insecure origins” the moment you call the API from an HTTP page.
Can I ask for location access without showing a permission prompt?
No, and that’s by design — every browser requires explicit user consent for geolocation, and there’s no API to bypass or pre-approve it. You can only check the existing permission state with navigator.permissions.query() before deciding whether to trigger the prompt.
What’s the difference between getCurrentPosition() and watchPosition()?
getCurrentPosition() fetches the location once. watchPosition() registers a callback that fires repeatedly as the device’s position changes, useful for live tracking (turn-by-turn navigation, live location sharing). It returns a watch ID you must pass to navigator.geolocation.clearWatch() when you’re done, or the browser keeps polling location indefinitely and draining battery.
Does enableHighAccuracy always give a better result?
Not always, and it costs more battery and time. On mobile, it asks the device to use GPS instead of (or in addition to) Wi-Fi/cell-tower triangulation, which is more accurate outdoors but can be slower indoors or fail entirely where GPS signal can’t penetrate. On laptops without GPS hardware, the flag often makes no measurable difference since Wi-Fi positioning is the only signal available either way.
See Also
- Complete Guide to Enabling HTTPS on Apache Tomcat
- Mastering Global Variables in JavaScript
- Jersey JAX-RS JSONP Example: Cross-Origin Requests with Callback Support
Conclusion
The Geolocation API itself hasn’t changed much since it shipped, but the environment around it has — HTTPS went from optional to mandatory, and the Permissions API gives you a way to ask “would this even work” before interrupting the user with a prompt. If your geolocation code only handles the happy path and a generic catch-all error, you’re missing the three cases that actually show up in production: an HTTP origin, a user who already said no, and a device that just can’t get a fix in time.