JavaScriptGeolocation

Geolocation API

The Geolocation API lets a web page ask the device for its current position. The browser is the gatekeeper: it shows a permission prompt, decides which signals to combine (GPS, Wi-Fi, IP), and returns a coordinate or an error. The API is tiny — three methods — but the workflow around permission and accuracy deserves care.

The starting point

Everything lives on navigator.geolocation. It is undefined on insecure origins — geolocation only works over https: (and localhost).

JS
if ("geolocation" in navigator) {
  // safe to use navigator.geolocation
} else {
  // unsupported, no fallback in the browser itself
}
getCurrentPosition — a one-shot reading

The most common call asks for the position once. It is callback-based, not promise-based, so wrap it if you want await.

JS
navigator.geolocation.getCurrentPosition(
  (pos) => {
    const { latitude, longitude, accuracy } = pos.coords;
    console.log(`${latitude}, ${longitude} (±${accuracy}m)`);
  },
  (err) => {
    console.warn("geolocation failed:", err.code, err.message);
  },
  {
    enableHighAccuracy: false,
    timeout: 10_000,
    maximumAge: 60_000,
  }
);
The position object

The success callback receives a GeolocationPosition with two fields:

  • timestamp — when the reading was taken.

  • coords — a GeolocationCoordinates with latitude, longitude, accuracy (in metres), and, where available, altitude, altitudeAccuracy, heading, speed.

On a laptop without GPS, you typically get accuracy of a few hundred metres derived from Wi-Fi / IP. On a phone with GPS enabled, sub-10 m is normal outdoors.

The error object

GeolocationPositionError codes

JS
// err.code === 1 → PERMISSION_DENIED  (user said no, or origin is blocked)
// err.code === 2 → POSITION_UNAVAILABLE (no signal, hardware error)
// err.code === 3 → TIMEOUT  (took longer than options.timeout)
watchPosition — a live stream

watchPosition calls your success callback every time the position changes by a meaningful amount. It returns a numeric id you pass to clearWatch to stop.

JS
const id = navigator.geolocation.watchPosition(
  (pos) => updateMap(pos.coords),
  (err) => console.warn(err),
  { enableHighAccuracy: true }
);

// later, when leaving the page or feature:
navigator.geolocation.clearWatch(id);
Battery drain
A live watch with `enableHighAccuracy: true` keeps the GPS warm and can drain a phone battery quickly. Always clear the watch when the user navigates away or pauses the feature.
Promisifying the API

The callback shape is awkward in modern code. A 4-line wrapper turns it into a promise.

JS
function getPosition(options) {
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(resolve, reject, options);
  });
}

try {
  const pos = await getPosition({ timeout: 8000 });
  console.log(pos.coords.latitude, pos.coords.longitude);
} catch (err) {
  console.warn("could not get position:", err.message);
}
The options object in detail
  • enableHighAccuracy (default false) — request GPS-level precision. Slower and more power-hungry; on desktop, often makes no difference.

  • timeout (default Infinity) — milliseconds before the request rejects with code 3.

  • maximumAge (default 0) — accept a cached reading up to this many ms old without re-measuring.

Permission flow

On the first call, the browser shows its own prompt — your code cannot style it or trigger it explicitly. The result is sticky: most browsers remember the choice per origin. You can check the state ahead of time with the Permissions API:

JS
const status = await navigator.permissions.query({ name: "geolocation" });

status.state;                  // "granted" | "denied" | "prompt"
status.addEventListener("change", () => console.log("now:", status.state));

That lets you show a contextual explainer before calling getCurrentPosition — much better than the cold native prompt.

Common UX shape

ask only when the user takes an action

JS
button.addEventListener("click", async () => {
  button.disabled = true;
  try {
    const pos = await getPosition({ enableHighAccuracy: true, timeout: 10_000 });
    showWeatherFor(pos.coords);
  } catch (err) {
    if (err.code === 1) banner("You blocked location access. Enable it in site settings.");
    if (err.code === 2) banner("We could not get a location signal.");
    if (err.code === 3) banner("Timed out — try again outdoors.");
  } finally {
    button.disabled = false;
  }
});
Privacy is the feature
Asking for location on page load is a near-instant rejection from most users. Tie the request to a clear user action (a "use my location" button) and explain what you do with the result.