
Introduction
The HTML5 Geolocation API (navigator.geolocation) gives browsers a direct mechanism to access a device's physical location — no third-party library required. Geofencing builds on top of it: define a virtual boundary in code, track the user's position continuously, and fire events when they cross that line.
The API itself is simple to call. The complexity lives one layer up:
- Choosing the right distance formula for boundary checks
- Configuring accuracy parameters that don't drain batteries
- Handling permission states that vary by browser
- Knowing when your geofence radius is too small to be reliable given GPS error margins
This guide walks through the exact steps to implement geofencing with the HTML5 Geolocation API in JavaScript — covering the boundary-check math, the options that affect accuracy, and the mistakes that cause missed or phantom triggers. Browser-based geofencing works well for lightweight, single-user scenarios — but understanding where it breaks down is just as important as knowing how to build it.
TL;DR
watchPosition()— notgetCurrentPosition()— is required for geofencing; it fires a callback each time the device position changes- Circular boundary checks use the Haversine formula to compute great-circle distance, compared against a defined radius in meters
- The API requires HTTPS and explicit user permission; HTTP origins receive
PERMISSION_DENIED - Three options control accuracy trade-offs:
enableHighAccuracy,maximumAge, andtimeout - For server-side, multi-asset, or logistics-scale geofencing, the HTML5 API can't handle the workload; a dedicated geofencing API is required
How to Implement Geofencing with the HTML5 Geolocation API in JavaScript
Step 1: Check for Browser Support and Handle Permissions
Before calling any geolocation method, verify the API exists:
if ("geolocation" in navigator) {
// safe to proceed
} else {
showManualLocationInput(); // degrade gracefully
}
The W3C Geolocation specification confirms the API is exposed only in secure contexts — HTTP origins return PERMISSION_DENIED automatically. Chrome enforced this in Chrome 50 (April 2016); Firefox followed in Firefox 55 (2017).
On the first call, the browser prompts for location access. Your error callback receives a GeolocationPositionError with one of three codes:
| Code | Constant | When it fires |
|---|---|---|
| 1 | PERMISSION_DENIED |
User denied, or page is on HTTP |
| 2 | POSITION_UNAVAILABLE |
Internal position-source error |
| 3 | TIMEOUT |
No position acquired within the timeout window |
Always include an error handler. A missing error callback means silent failure — the geofencing logic simply never runs and you won't know why.
Step 2: Retrieve the User's Location Continuously with watchPosition()
getCurrentPosition() retrieves a single fix and stops. For geofencing, you need watchPosition() — it registers a handler that fires each time the device's position changes.
const options = {
enableHighAccuracy: true, // Request GPS-level precision
timeout: 10000, // Error after 10 seconds with no fix
maximumAge: 0 // Always request a fresh position
};
const watchId = navigator.geolocation.watchPosition(
successCallback,
errorCallback,
options
);
Store the watchId — you'll need it to stop the watcher with navigator.geolocation.clearWatch(watchId) when monitoring is no longer needed.
The three options control the accuracy/performance balance:
enableHighAccuracy:truerequests GPS over cell/Wi-Fi triangulation — more precise, more battery drainmaximumAge: how old a cached position can be (milliseconds);0forces a fresh reading every timetimeout: how long to wait before firing the error callback

Step 3: Define Your Geofence Boundaries
A circular geofence needs three values:
const geofence = {
lat: 35.2271, // center latitude
lng: -80.8431, // center longitude
radius: 200 // radius in meters
};
For irregular shapes (delivery zones, building footprints, restricted areas) a polygon geofence uses an array of lat/lng vertex pairs:
const polygonFence = [
{ lat: 35.228, lng: -80.844 },
{ lat: 35.229, lng: -80.842 },
{ lat: 35.227, lng: -80.841 },
// ... more vertices
];
If your geofence definitions come from a server (fleet delivery zones, dynamic job sites), fetch them before starting the watcher so the check logic has boundaries ready on the first position callback.
Step 4: Write the Boundary Check Logic
With boundaries defined, the next step is checking whether a position falls inside them. The two algorithms below handle circular and polygon fences separately.
For circular geofences, use the Haversine formula — it calculates the great-circle distance between two lat/lng points. Scikit-learn's documentation defines this as the angular distance between two points on a sphere's surface, with coordinates in radians.
function haversineDistance(lat1, lng1, lat2, lng2) {
const R = 6371000; // Earth radius in meters
const toRad = deg => deg * (Math.PI / 180);
const dLat = toRad(lat2 - lat1);
const dLng = toRad(lng2 - lng1);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
Math.sin(dLng / 2) ** 2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
// Usage
const distance = haversineDistance(
position.coords.latitude,
position.coords.longitude,
geofence.lat,
geofence.lng
);
const isInside = distance <= geofence.radius;
For polygon geofences, use the ray-casting algorithm: cast a ray from the test point in any direction and count how many times it crosses the polygon's edges. An odd count means the point is inside; even means outside. Use this approach when your boundaries follow roads, building outlines, or service zones that no circle can approximate cleanly.
Step 5: Trigger Actions on Entry and Exit Events
The key is maintaining state between callbacks. A single boolean tracks whether the user was inside on the previous check:
let isInsideGeofence = false;
function successCallback(position) {
const distance = haversineDistance(
position.coords.latitude,
position.coords.longitude,
geofence.lat,
geofence.lng
);
const currentlyInside = distance <= geofence.radius;
if (!isInsideGeofence && currentlyInside) {
onEnter(); // User just crossed in
} else if (isInsideGeofence && !currentlyInside) {
onExit(); // User just crossed out
}
isInsideGeofence = currentlyInside;
}
When the session ends, clean up:
navigator.geolocation.clearWatch(watchId);
In React or Vue, that call belongs in the component's unmount/cleanup lifecycle hook.
Skipping cleanup leaves the watcher running in the background — burning battery and CPU until the tab closes.

When Should You Use the HTML5 Geolocation API for Geofencing?
This approach fits well for client-side, browser-based applications monitoring a single user's device. Practical scenarios:
- Notifying a user when they approach a retail location
- Confirming a field technician has arrived at a job site
- Restricting feature access to users within a defined service area
Where it falls short:
- Server-side verification: Browser coordinates can be spoofed; if your application requires trusted location data, the check must run server-side
- Multi-asset monitoring: Tracking hundreds of fleet vehicles simultaneously requires server infrastructure, not client-side JavaScript
- Background persistence: Per the W3C editor's draft, position acquisition pauses when a document's
visibilityStateishidden; active watches may not deliver updates to backgrounded pages. Geofencing stops entirely when the tab closes - Native mobile parity: iOS Core Location and the Android Geofencing API run at the OS level, survive app restarts, and use battery-optimized monitoring — none of which browser JavaScript can match
Key Parameters That Affect Geofencing Accuracy and Performance
Misconfiguring these is the most common cause of missed or false triggers.
enableHighAccuracy
Setting this to true requests GPS-level precision rather than cell or Wi-Fi triangulation. The FAA documents basic GPS accuracy at approximately 7.0 meters (95% of the time). Higher accuracy is worth the battery cost for small geofences under 100 meters; for large zones, the cost outweighs the precision gain.
maximumAge
This controls how old a cached position can be before the browser fetches a fresh one. Set it too high and reported positions lag behind the user's actual location, so entry and exit events arrive late. For geofencing, 0 is the safe default.
timeout
How long the browser waits before calling the error callback when no position is acquired. A value around 10000 (10 seconds) is reasonable. Handle timeouts gracefully: don't let them crash the geofencing logic. Just retry or display a status message.
Those three options control how the browser collects position data. The next factor operates at a different level — it determines whether your geofence geometry is even compatible with the accuracy you're getting.
Geofence Radius vs. GPS Accuracy
The position.coords.accuracy property returns the 95% confidence radius in meters for the returned coordinates. If your geofence radius is smaller than that value, boundary detection becomes unreliable — the device's reported position can legitimately sit anywhere within that circle.
A research study on urban GPS positioning found that in severe urban canyon environments, horizontal error can reach 13.0 meters RMSE due to multipath interference — buildings reflecting satellite signals. Always read position.coords.accuracy before acting on an event:
if (position.coords.accuracy > geofence.radius) {
console.warn("GPS accuracy too low for this geofence radius");
return; // Skip this reading
}

A practical minimum: set your geofence radius to the reported accuracy value plus at least 20–30 meters of buffer. That buffer absorbs the natural drift between successive readings.
Common Mistakes and Troubleshooting
No error callback
Every watchPosition() and getCurrentPosition() call needs an error handler. Without one, permission denial, HTTPS violations, and position failures all fail silently. Always provide a fallback — a manual location input works well when geolocation is blocked.
Using getCurrentPosition() inside setInterval()
This is an anti-pattern. Polling with setInterval() wastes battery, causes position jumps between readings, and misses boundary crossings that happen between intervals. watchPosition() is purpose-built for continuous monitoring and handles all of this natively.
Geofence radius smaller than GPS accuracy
Entry/exit events fire randomly near the boundary even when the user is stationary — a reliable sign your radius is too tight. Read position.coords.accuracy and add it as a buffer to your geofence radius, or increase the radius until it's reliably larger than the worst-case accuracy for your deployment environment.
Watcher and memory leaks
Calling watchPosition() without eventually calling clearWatch() leaves a watcher running indefinitely. Calling watchPosition() multiple times without clearing previous watchers stacks up redundant callbacks. In component-based frameworks, always tie clearWatch() to the unmount lifecycle hook.

Catching these issues early keeps your geolocation implementation stable across browsers and devices — the foundation for any reliable geofencing feature.
Alternatives to the HTML5 Geolocation API for Geofencing
Native Mobile Geofencing (iOS Core Location / Android Geofencing API)
When your application is a native mobile app, native OS geofencing is the right choice. Apple's documentation confirms that iOS region monitoring can wake or relaunch an app when a boundary is crossed — even if the app is not running. Android's Geofencing API maintains registered geofences across service restarts and, on Android 8.0+, processes background geofence responses approximately every few minutes. Neither capability is achievable with browser JavaScript.
Server-Side Geofencing with a Dedicated API
For applications tracking multiple assets simultaneously — fleet management, last-mile delivery, field service dispatch — geofencing logic belongs server-side. Client-side JavaScript can't reliably serve hundreds of concurrent devices, handle location verification server-side, or survive a closed browser tab.
Dedicated platforms like NextBillion.ai's Geofencing API are built for this workload. The API supports polygons, circles, corridors, and custom shapes, with real-time automated actions when assets cross boundaries. Geofences feed directly into the routing engine as constraint zones, and the platform carries a 99.9% uptime SLA.

Use cases the HTML5 API can't touch include:
- Proof-of-delivery confirmation at the drop-off location
- Depot check-in logging as vehicles arrive or depart
- Automated customer ETA notifications triggered by zone entry
- Restricted-zone routing enforcement as a hard constraint
A real example: CriticaLog, which manages 300–400 vehicles across two daily shifts, implemented NextBillion.ai's Geofencing API to set 2-kilometer radius fences around hubs and up to 5-kilometer fences around customer premises. The implementation used webhook notifications for entry/exit events — no continuous live tracking required — which minimized app performance overhead while still automating dispatch and delivery alerts.
IP-Based Geolocation as a Fallback
IP geolocation resolves a user's approximate location from their IP address — no permission prompt required. The trade-off is precision: accuracy is typically city or region level, making it unsuitable for anything requiring meter-level boundary checks. Use it for coarse controls like country-level content gating, not for geofencing where the boundary matters at the street level.
Frequently Asked Questions
What is the difference between getCurrentPosition() and watchPosition()?
getCurrentPosition() retrieves a single location fix and stops. watchPosition() registers a persistent handler that fires each time the device's position changes. Geofencing requires watchPosition() — you need continuous position updates to detect boundary crossings in real time.
Does the HTML5 Geolocation API work on all browsers?
All major browsers support it — Chrome, Firefox, Safari, and Edge. However, it requires HTTPS and explicit user permission. MDN also notes it may be unavailable in China, where the Wi-Fi positioning services it relies on are often inaccessible.
How do you check if a GPS coordinate is inside a circular geofence in JavaScript?
Use the Haversine formula to calculate the great-circle distance in meters between the user's current lat/lng and the geofence center. If that distance is less than or equal to the geofence radius, the point is inside. See Step 4 above for the full function.
Why is geolocation permission being denied in my web app?
Three causes account for most cases:
- The page is served over HTTP, not HTTPS — this triggers automatic
PERMISSION_DENIED - The user previously denied permission, which persists until manually reset in browser settings
- Location services are disabled at the OS level, blocking all browser access
Can HTML5 geofencing work when the browser tab is in the background?
Not reliably. The W3C spec restricts position updates to fully active, visible documents, so updates can be dropped when a tab is hidden. True background geofencing requires native mobile APIs or a Service Worker with background sync — both have significant limitations in browsers.
How accurate is the HTML5 Geolocation API for geofencing?
The FAA documents basic GPS accuracy at approximately 7.0 meters (95% confidence) under open-sky conditions. In urban canyons, multipath errors can push this to 13+ meters. The position.coords.accuracy property returns the 95% confidence radius in meters for each fix — always check it before triggering a geofence event.


