JS Geolocation API Guide: A Complete Tutorial
A complete tutorial on the JavaScript Geolocation API. Covers navigator.geolocation, getCurrentPosition, watchPosition, clearWatch, position coordinates, accuracy and altitude, error handling with GeolocationPositionError, permission states with Permissions API, options like enableHighAccuracy and timeout, and building a location service wrapper.
The Geolocation API lets web applications access the user's geographic location with their permission. It provides one-time lookups, continuous tracking, and detailed position data including coordinates, altitude, speed, and heading. This guide covers every method and error scenario.
Checking for Support
if ("geolocation" in navigator) {
console.log("Geolocation is supported");
} else {
console.log("Geolocation is not supported by this browser");
}Getting Current Position
navigator.geolocation.getCurrentPosition(
// Success callback
(position) => {
console.log("Latitude:", position.coords.latitude);
console.log("Longitude:", position.coords.longitude);
console.log("Accuracy:", position.coords.accuracy, "meters");
console.log("Timestamp:", new Date(position.timestamp));
},
// Error callback
(error) => {
console.error(`Geolocation error (${error.code}): ${error.message}`);
},
// Options
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0,
}
);Position Object Properties
| Property | Type | Description |
|---|---|---|
coords.latitude | number | Latitude in decimal degrees |
coords.longitude | number | Longitude in decimal degrees |
coords.accuracy | number | Accuracy radius in meters |
coords.altitude | number or null | Altitude in meters above sea level |
coords.altitudeAccuracy | number or null | Altitude accuracy in meters |
coords.heading | number or null | Direction of travel in degrees (0-360) |
coords.speed | number or null | Speed in meters per second |
timestamp | number | Time the position was acquired (ms since epoch) |
Options Object
| Option | Type | Default | Description |
|---|---|---|---|
enableHighAccuracy | boolean | false | Request GPS-level accuracy (slower, uses more battery) |
timeout | number | Infinity | Maximum time (ms) to wait for a position |
maximumAge | number | 0 | Accept cached position if younger than this (ms) |
// Fast, low-power position (cell tower / Wi-Fi)
const lowAccuracyOptions = {
enableHighAccuracy: false,
timeout: 5000,
maximumAge: 60000, // Accept 1-minute-old cache
};
// Precise GPS position (slower, battery-intensive)
const highAccuracyOptions = {
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 0, // Always get fresh position
};
// Balance: try high accuracy, fall back to cached
const balancedOptions = {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 30000, // Accept 30-second cache
};Error Handling
function handleGeolocationError(error) {
switch (error.code) {
case error.PERMISSION_DENIED:
console.error("User denied geolocation permission");
showMessage("Please enable location access in your browser settings.");
break;
case error.POSITION_UNAVAILABLE:
console.error("Position information is unavailable");
showMessage("Unable to determine your location. Check GPS/Wi-Fi.");
break;
case error.TIMEOUT:
console.error("Geolocation request timed out");
showMessage("Location request timed out. Please try again.");
break;
default:
console.error("Unknown geolocation error:", error.message);
showMessage("An unexpected error occurred.");
}
}
navigator.geolocation.getCurrentPosition(
(pos) => console.log(pos.coords.latitude, pos.coords.longitude),
handleGeolocationError,
{ enableHighAccuracy: true, timeout: 10000 }
);Error Code Reference
| Code | Constant | Cause |
|---|---|---|
| 1 | PERMISSION_DENIED | User or browser blocked location access |
| 2 | POSITION_UNAVAILABLE | Device cannot determine position (no GPS/Wi-Fi) |
| 3 | TIMEOUT | Position request exceeded the timeout option |
Watching Position (Continuous Tracking)
let watchId = null;
function startTracking() {
watchId = navigator.geolocation.watchPosition(
(position) => {
console.log("Updated position:");
console.log(` Lat: ${position.coords.latitude}`);
console.log(` Lng: ${position.coords.longitude}`);
console.log(` Accuracy: ${position.coords.accuracy}m`);
if (position.coords.speed !== null) {
console.log(` Speed: ${position.coords.speed} m/s`);
}
},
(error) => {
handleGeolocationError(error);
if (error.code === error.PERMISSION_DENIED) {
stopTracking();
}
},
{
enableHighAccuracy: true,
timeout: 20000,
maximumAge: 5000,
}
);
}
function stopTracking() {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
console.log("Tracking stopped");
}
}
// Start
startTracking();
// Stop after 5 minutes
setTimeout(stopTracking, 5 * 60 * 1000);For advanced tracking with distance calculation and geofencing, see tracking user location with JavaScript geolocation.
Permission State Detection
async function checkGeolocationPermission() {
if (!navigator.permissions) {
return "unknown"; // Permissions API not supported
}
try {
const status = await navigator.permissions.query({ name: "geolocation" });
console.log("Current permission:", status.state);
// "granted", "denied", or "prompt"
// Listen for permission changes
status.addEventListener("change", () => {
console.log("Permission changed to:", status.state);
});
return status.state;
} catch {
return "unknown";
}
}
// Conditional UI based on permission state
async function showLocationUI() {
const permission = await checkGeolocationPermission();
switch (permission) {
case "granted":
// Silently get location without prompt
getLocation();
break;
case "denied":
showSettingsInstructions();
break;
case "prompt":
showLocationRequestButton();
break;
default:
showLocationRequestButton();
}
}Promise-Based Wrapper
function getPosition(options = {}) {
return new Promise((resolve, reject) => {
if (!("geolocation" in navigator)) {
reject(new Error("Geolocation is not supported"));
return;
}
navigator.geolocation.getCurrentPosition(resolve, reject, options);
});
}
// Usage with async/await
async function showUserLocation() {
try {
const position = await getPosition({
enableHighAccuracy: true,
timeout: 10000,
});
const { latitude, longitude, accuracy } = position.coords;
console.log(`Location: ${latitude}, ${longitude} (+/- ${accuracy}m)`);
} catch (error) {
handleGeolocationError(error);
}
}Location Service Wrapper
class LocationService {
constructor(defaultOptions = {}) {
this.options = {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0,
...defaultOptions,
};
this.watchId = null;
this.lastPosition = null;
this.listeners = new Set();
}
async getPermission() {
if (!navigator.permissions) return "unknown";
try {
const result = await navigator.permissions.query({ name: "geolocation" });
return result.state;
} catch {
return "unknown";
}
}
getCurrentPosition(options = {}) {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
(position) => {
this.lastPosition = position;
resolve(position);
},
reject,
{ ...this.options, ...options }
);
});
}
startWatching(options = {}) {
if (this.watchId !== null) {
this.stopWatching();
}
this.watchId = navigator.geolocation.watchPosition(
(position) => {
this.lastPosition = position;
this.notifyListeners(position);
},
(error) => {
this.notifyListeners(null, error);
},
{ ...this.options, ...options }
);
}
stopWatching() {
if (this.watchId !== null) {
navigator.geolocation.clearWatch(this.watchId);
this.watchId = null;
}
}
onPositionUpdate(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
notifyListeners(position, error = null) {
for (const listener of this.listeners) {
listener(position, error);
}
}
getLastKnown() {
return this.lastPosition;
}
isWatching() {
return this.watchId !== null;
}
async getCoordinates(options = {}) {
const position = await this.getCurrentPosition(options);
return {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
};
}
destroy() {
this.stopWatching();
this.listeners.clear();
this.lastPosition = null;
}
}
// Usage
const location = new LocationService({ enableHighAccuracy: true });
const coords = await location.getCoordinates();
console.log(coords); // { latitude: 30.26, longitude: -97.74, accuracy: 15 }
// Continuous tracking
const unsub = location.onPositionUpdate((pos, err) => {
if (err) {
console.error("Location error:", err.message);
return;
}
console.log(`Moved to: ${pos.coords.latitude}, ${pos.coords.longitude}`);
});
location.startWatching();
// Later
unsub();
location.destroy();Rune AI
Key Insights
- Always handle errors: Users can deny permission, GPS can be unavailable, and requests can time out; handle all three error codes
- Permission state check first: Use the Permissions API to check the current state before calling
getCurrentPositionto avoid unnecessary prompts - enableHighAccuracy trades speed for precision: GPS is 3-10m accurate but slower and power-hungry; Wi-Fi/cell is faster but 20-3000m accuracy
- watchPosition for continuous tracking: Returns a
watchIdforclearWatch; always stop watching when no longer needed to save battery - HTTPS required: Geolocation is blocked on non-secure origins; localhost is the only exception for development
Frequently Asked Questions
Does the Geolocation API work over HTTP?
How accurate is browser geolocation?
Can I get a user's location without their permission?
What is the difference between getCurrentPosition and watchPosition?
How do I handle the case where the user has denied permission permanently?
Conclusion
The Geolocation API provides one-time and continuous location access with detailed coordinate data. Always handle all three error codes, check permission state before prompting, use enableHighAccuracy only when needed to save battery, and wrap callbacks in Promises for cleaner async/await code. For continuous tracking with distance and geofencing, see tracking user location with JavaScript geolocation. For browser storage of location data, see JS localStorage API guide.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.