Skip to content
JavaScript js browser 5 min read

Geolocation & Device APIs

Modern browsers expose a rich set of device capabilities through the global navigator object — from a user’s physical location to the system clipboard, native share sheet, and haptic feedback. These APIs let web apps feel native, but every one of them is gated behind a permission prompt or a secure context (HTTPS) because they touch sensitive hardware and personal data. This page covers the Geolocation API in depth, then tours the most useful companion device APIs and the permission model that ties them together.

The Geolocation API

The Geolocation API lives at navigator.geolocation and reports the device’s position using GPS, Wi-Fi, cell towers, or IP — the browser picks the best available source. It is asynchronous and callback-based: you supply a success handler and an error handler. Geolocation only works in a secure context (HTTPS or localhost), and the browser always asks the user for permission the first time.

Getting the current position once

getCurrentPosition() takes a success callback, an optional error callback, and an optional options object. The success callback receives a GeolocationPosition whose coords property holds the data you care about.

navigator.geolocation.getCurrentPosition(
  (position) => {
    const { latitude, longitude, accuracy } = position.coords;
    console.log(`You are at ${latitude}, ${longitude} (±${accuracy}m)`);
  },
  (error) => {
    console.error(`Geolocation failed: ${error.message}`);
  },
  { enableHighAccuracy: true, timeout: 10_000, maximumAge: 0 }
);

Output:

You are at 37.7749, -122.4194 (±20m)

Because the API predates promises, it is common to wrap it so you can await a position:

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

try {
  const pos = await getPosition({ enableHighAccuracy: true });
  console.log(pos.coords.latitude, pos.coords.longitude);
} catch (err) {
  console.error("Could not read location:", err.message);
}

Position options

The third argument fine-tunes accuracy, freshness, and how long you are willing to wait.

OptionTypeDefaultDescription
enableHighAccuracybooleanfalseRequest the most precise source (GPS), at the cost of battery and speed.
timeoutnumber (ms)InfinityMax time to wait before firing the error callback with TIMEOUT.
maximumAgenumber (ms)0Accept a cached position up to this age instead of fetching fresh.

Watching position changes

For navigation or live tracking, watchPosition() invokes your success callback every time the device moves significantly. It returns a numeric watch ID that you pass to clearWatch() to stop listening — always clean up to save battery.

const watchId = navigator.geolocation.watchPosition(
  (pos) => updateMap(pos.coords.latitude, pos.coords.longitude),
  (err) => console.warn(err.message),
  { enableHighAccuracy: true, maximumAge: 5_000 }
);

// Later, when the view is closed:
navigator.geolocation.clearWatch(watchId);

Handling errors

The error callback receives a GeolocationPositionError with a code you should branch on, since a denied permission needs very different UX from a timeout.

function onError(error) {
  switch (error.code) {
    case error.PERMISSION_DENIED:
      return showMessage("Enable location access to use this feature.");
    case error.POSITION_UNAVAILABLE:
      return showMessage("Location signal is unavailable right now.");
    case error.TIMEOUT:
      return showMessage("Locating took too long — please retry.");
  }
}

Calling getCurrentPosition() on http:// (anything but localhost) silently fails with a PERMISSION_DENIED error in modern browsers. Serve over HTTPS during development with tools like a local TLS proxy or your framework’s --https flag.

A live geolocation demo

<button id="locate">Find my location</button>
<p id="result">Click the button and approve the prompt.</p>
<script>
  const out = document.getElementById("result");
  document.getElementById("locate").addEventListener("click", () => {
    if (!("geolocation" in navigator)) {
      out.textContent = "Geolocation is not supported here.";
      return;
    }
    out.textContent = "Locating…";
    navigator.geolocation.getCurrentPosition(
      ({ coords }) => {
        out.textContent =
          `Lat: ${coords.latitude.toFixed(4)}, ` +
          `Lng: ${coords.longitude.toFixed(4)} (±${Math.round(coords.accuracy)}m)`;
      },
      (err) => { out.textContent = `Error: ${err.message}`; },
      { enableHighAccuracy: true, timeout: 10000 }
    );
  });
</script>

Checking permissions ahead of time

The Permissions API lets you read the current state of a permission without triggering a prompt, so you can tailor your UI. Query "geolocation" and inspect the state, which is "granted", "denied", or "prompt".

const status = await navigator.permissions.query({ name: "geolocation" });
console.log(status.state); // "prompt" | "granted" | "denied"

status.addEventListener("change", () => {
  console.log("Permission changed to", status.state);
});

Output:

prompt

Other navigator & device APIs

Beyond location, navigator surfaces several focused device capabilities. All require a secure context, and most need a transient user activation — they must run inside a click or tap handler, not on page load.

Clipboard API

The async Clipboard API reads and writes text (and richer data) with navigator.clipboard. Writing usually works inside a user gesture; reading prompts for permission.

copyBtn.addEventListener("click", async () => {
  try {
    await navigator.clipboard.writeText("npm install devcraftly");
    console.log("Copied!");
  } catch {
    console.error("Clipboard write was blocked.");
  }
});

Web Share API

navigator.share() opens the operating system’s native share sheet, returning a promise that resolves when the user finishes. Feature-detect it, since desktop browsers often lack it.

if (navigator.share) {
  await navigator.share({
    title: "DevCraftly",
    text: "Great JS docs!",
    url: location.href,
  });
}

Vibration API

On supported mobile devices, navigator.vibrate() triggers haptic feedback. Pass a duration in milliseconds or a pattern array of vibrate/pause durations; pass 0 or [] to cancel.

<button id="buzz">Buzz me</button>
<p id="note"></p>
<script>
  document.getElementById("buzz").addEventListener("click", () => {
    const note = document.getElementById("note");
    if (navigator.vibrate) {
      navigator.vibrate([200, 100, 200]); // buzz, pause, buzz
      note.textContent = "Vibration triggered (mobile only).";
    } else {
      note.textContent = "Vibration API not supported on this device.";
    }
  });
</script>

Quick capability reference

APIEntry pointSecure contextNeeds user gesture
Geolocationnavigator.geolocationYesNo (but prompts)
Clipboardnavigator.clipboardYesUsually
Web Sharenavigator.share()YesYes
Vibrationnavigator.vibrate()NoYes

Best practices

  • Always feature-detect ("geolocation" in navigator, navigator.share) before calling — support varies widely across devices and browsers.
  • Request location only in response to a user action and explain why first; unsolicited prompts get denied.
  • Use enableHighAccuracy: true sparingly; it drains battery and is slower, so prefer it only for active navigation.
  • Always pair watchPosition() with clearWatch() when the view unmounts to stop background GPS usage.
  • Set a sensible timeout and handle every GeolocationPositionError code with a distinct, actionable message.
  • Use the Permissions API to detect a "denied" state and guide users to re-enable access instead of repeatedly failing.
  • Never block core functionality behind a device API — provide a manual fallback (e.g., a postal-code input) when permission is refused.
Last updated June 1, 2026
Was this helpful?