# Geolocation API

> getCurrentPosition() and watchPosition() report the device position after a permission prompt. PositionOptions members, the three error codes, and fallbacks.

`navigator.geolocation` reports the device's position (latitude, longitude, accuracy radius, and optional altitude, heading, and speed) through a one-shot `getCurrentPosition()` or a continuous `watchPosition()`, after the browser has shown its own permission prompt. Nothing is returned synchronously: results and failures arrive in the callbacks you pass.

Every engine has shipped it since Chrome 5, Firefox 3.5, and Safari 5 (iOS 3). The secure-context requirement arrived later: Chrome 50, Firefox 55, and Safari 10 reject requests from `http://` origins other than `localhost` with `PERMISSION_DENIED` while still exposing `navigator.geolocation` (BCD `api.Geolocation.secure_context_required`). `GeolocationCoordinates.toJSON()` is newer still: Chrome 126, Firefox 129, Safari 18.

## Syntax

```js
navigator.geolocation.getCurrentPosition(successCallback)
navigator.geolocation.getCurrentPosition(successCallback, errorCallback)
navigator.geolocation.getCurrentPosition(successCallback, errorCallback, options)

navigator.geolocation.watchPosition(successCallback)
navigator.geolocation.watchPosition(successCallback, errorCallback)
navigator.geolocation.watchPosition(successCallback, errorCallback, options)

navigator.geolocation.clearWatch(watchId)
```

`getCurrentPosition()` returns `undefined`. `watchPosition()` returns a `long` watch id greater than zero, invokes `successCallback` each time the position changes until `clearWatch(watchId)` is called, and returns `0` when the document is not fully active. No user activation is required, but Chrome logs a `[Violation]` when the first request is not inside one (see the observed callout).

## Parameters

The two request methods share three parameters.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `successCallback` | `PositionCallback` | Yes | Receives a `GeolocationPosition` with `coords` (a `GeolocationCoordinates`) and `timestamp` (an `EpochTimeStamp`). |
| `errorCallback` | `PositionErrorCallback?` | No | Receives a `GeolocationPositionError` with `code` and `message`. Without it, failures are silent. |
| `options` | `PositionOptions` | No | The dictionary below. |

`PositionOptions` has three members, all optional.

| Member | Type | Required | Description |
|---|---|---|---|
| `enableHighAccuracy` | `boolean` | No | Default `false`. A hint to use the most accurate source available (GPS on phones), at the cost of slower first fix and more battery. A cached position is reused only when its `[[isHighAccuracy]]` flag matches this value. |
| `timeout` | `[Clamp] unsigned long` | No | Default `0xFFFFFFFF` ms. Maximum time to wait once acquisition starts; time spent waiting for the permission prompt or for a hidden document to become visible does not count. `0` can fail immediately. |
| `maximumAge` | `[Clamp] unsigned long` | No | Default `0`. Accept a cached position no older than this many milliseconds; `0` forces a fresh fix. |

## Exceptions

Neither method throws. Failures reach `errorCallback` as a `GeolocationPositionError` whose `code` is one of three constants on the interface.

| `code` | Constant | Condition |
|---|---|---|
| `1` | `PERMISSION_DENIED` | The user or system denied the permission; the document is not allowed to use the `geolocation` Permissions Policy feature; or the environment is a non-secure context. |
| `2` | `POSITION_UNAVAILABLE` | The document is not fully active; or the underlying system failed to acquire a position. |
| `3` | `TIMEOUT` | `options.timeout` elapsed after acquisition started. |

`message` is implementation-defined text for developers, not for display. Chrome reports permission failures with `User denied Geolocation` and Permissions Policy failures with `Geolocation has been disabled in this document by permissions policy.` A `PERMISSION_DENIED` from the user persists until they change the setting themselves; repeated calls do not re-prompt.

:::observed
In Chrome, calling `getCurrentPosition()` on page load rather than from a click prints `[Violation] Only request geolocation information in response to a user gesture.` in the DevTools Console, and a request from a document whose `Permissions-Policy` header excludes `geolocation` prints `Geolocation access has been blocked because of a permissions policy applied to the current document. See https://crbug.com/414348233 for more details.` while the error callback receives `code` `1`. Both strings are defined in Chromium's [`geolocation.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/geolocation/geolocation.cc) (chromium.googlesource.com).
:::

## Examples

Each example checks `"geolocation" in navigator` and gives the user a manual way to supply a location when the API is missing or the request fails. Triggering the request from a button keeps the prompt expected and avoids Chrome's violation warning.

### A one-shot fix with a manual address fallback

A `timeout` and a non-zero `maximumAge` keep the request from hanging on devices with no fix available. Every error code lands in the same fallback: showing the address field.

```js
const addressField = document.querySelector("#address");

function useManualEntry(reason) {
  addressField.hidden = false;
  addressField.placeholder = reason;
}

document.querySelector("#use-location").addEventListener("click", () => {
  if (!("geolocation" in navigator)) {
    useManualEntry("Enter your city");
    return;
  }
  navigator.geolocation.getCurrentPosition(
    ({ coords }) => {
      showNearby(coords.latitude, coords.longitude, coords.accuracy);
    },
    (error) => {
      const reasons = { 1: "Location access denied", 2: "Location unavailable", 3: "Location timed out" };
      useManualEntry(reasons[error.code] ?? error.message);
    },
    { enableHighAccuracy: false, timeout: 10_000, maximumAge: 60_000 },
  );
});
```

`coords.accuracy` is the 95 % confidence radius in metres; a "near me" feature can treat anything under a few hundred metres as good enough without `enableHighAccuracy`.

### Tracking a route and stopping when the view closes

`watchPosition()` keeps the radio on until `clearWatch()` runs, so tie the watch to the lifetime of the UI that needs it.

```js
let watchId = null;

function startTracking(onMove) {
  if (!("geolocation" in navigator)) return false;
  watchId = navigator.geolocation.watchPosition(
    (position) => onMove(position.coords),
    (error) => {
      stopTracking();
      console.warn(`Tracking stopped: ${error.code} ${error.message}`);
    },
    { enableHighAccuracy: true, maximumAge: 0 },
  );
  return watchId !== 0;
}

function stopTracking() {
  if (watchId !== null) navigator.geolocation.clearWatch(watchId);
  watchId = null;
}

document.querySelector("#start").addEventListener("click", () => startTracking(updateMap));
document.querySelector("#stop").addEventListener("click", stopTracking);
window.addEventListener("pagehide", stopTracking);
```

A `0` return value means the document was not fully active when the watch was requested, so the function reports failure instead of waiting for an error callback that may not come.

### Checking the permission state before prompting

`navigator.permissions.query({ name: "geolocation" })` reads the state without triggering a prompt, which lets the page show a "Use my location" button only when it can succeed or explain how to re-enable a denied permission.

```js
async function locationButtonState() {
  if (!("geolocation" in navigator)) return "unsupported";
  if (!navigator.permissions) return "unknown"; // request on click and handle the error
  const { state } = await navigator.permissions.query({ name: "geolocation" });
  return state; // "granted" | "prompt" | "denied"
}

const state = await locationButtonState();
document.querySelector("#use-location").hidden = state === "unsupported" || state === "denied";
document.querySelector("#denied-help").hidden = state !== "denied";
```

The `"unknown"` branch matters for older WebViews that have `geolocation` but not `navigator.permissions`; there the page falls back to requesting on click and reading the error code.

## See also

- [The notification permission model](/reference/notifications/permissions/), the same `granted` / `prompt` / `denied` model applied to another capability
- [Generic Sensor API](/reference/capabilities/sensors/), for orientation and motion rather than position
- [Local Network Access](/reference/capabilities/local-network-access/), another permission-gated capability
- [Geolocation API: getCurrentPosition() method](https://www.w3.org/TR/geolocation/#getcurrentposition-method) (w3.org)
- [Geolocation API: PositionOptions dictionary](https://www.w3.org/TR/geolocation/#position_options_interface) (w3.org)
- [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API) (developer.mozilla.org)