Documentation
Everything you need to integrate background location tracking into your Capacitor app.
Quick Install
npm install capacitor-bglocationnpx cap syncGetting Started
Install the plugin, configure tracking, and get your first location update in under 5 minutes.
1. Install
npm install capacitor-bglocation npx cap sync
2. Configure & Start
import { BackgroundLocation } from 'capacitor-bglocation';
// Configure the plugin (must be called before start)
await BackgroundLocation.configure({
distanceFilter: 15, // meters between updates
desiredAccuracy: 'high', // 'high' | 'balanced' | 'low'
heartbeatInterval: 15, // seconds
locationUpdateInterval: 5000, // ms (Android only)
debug: true, // verbose logging
});
// Listen for location updates
BackgroundLocation.addListener('onLocation', (location) => {
console.log(location.latitude, location.longitude);
});
// Start tracking
await BackgroundLocation.start();3. Stop Tracking
await BackgroundLocation.stop(); await BackgroundLocation.removeAllListeners();
Permissions
On Android 10+, request foreground permission first, then background. On iOS, call requestPermissions() — the system handles the Always/When In Use flow. See Platform Guides for details.
Configuration
All options available in configure(). Call before start().
Core Options
| Option | Type | Default | Description |
|---|---|---|---|
distanceFilter | number | 'auto' | 15 | Minimum distance in meters between location updates, or 'auto' for speed-adaptive mode. |
desiredAccuracy | string | 'high' | 'high' | 'balanced' | 'low' |
heartbeatInterval | number | 15 | Heartbeat interval in seconds. Fires even when stationary. |
locationUpdateInterval | number | 5000 | Update interval in ms. Android only. |
debug | boolean | false | Enable verbose logging and onDebug events. |
HTTP Posting
Send location updates directly from native code — no JS bridge overhead. Includes automatic offline buffering with retry.
await BackgroundLocation.configure({
distanceFilter: 15,
desiredAccuracy: 'high',
heartbeatInterval: 15,
locationUpdateInterval: 5000,
fastestLocationUpdateInterval: 2000,
http: {
url: 'https://api.example.com/locations',
headers: {
Authorization: 'Bearer <token>',
},
buffer: {
maxSize: 1000, // offline buffer capacity
},
},
});Each location is POSTed as JSON to the configured URL. The request is made from native code (URLSession on iOS, HttpURLConnection on Android) — no JS bridge overhead.
HTTP POST Body
Every onLocation event triggers a POST with this JSON body:
// POST https://api.example.com/locations
// Content-Type: application/json
{
"location": {
"latitude": 52.2297,
"longitude": 21.0122,
"accuracy": 5.0,
"speed": 1.2,
"heading": 180.0,
"altitude": 110.5,
"timestamp": 1700000000000,
"isMoving": true,
"isMock": false
}
}Heartbeats, getCurrentPosition(), and start/stop events do not trigger HTTP requests — only location updates do.
HttpEvent (onHttp)
Every POST result fires the onHttp event:
interface HttpEvent {
statusCode: number; // HTTP code (0 = network error)
success: boolean; // true for 2xx responses
responseText: string; // response body
error?: string; // only present on network errors
bufferedCount?: number; // items in offline buffer (0 when empty)
}Offline Buffer & Retry
When buffer.maxSize is set, failed POSTs are stored in a local SQLite database. On the next successful request, buffered locations are flushed automatically in batches of 50 (FIFO order). Flush stops on the first failure. When the buffer is full, the oldest entries are dropped. Without a buffer configured, failed requests are lost.
Adaptive Distance Filter
Set distanceFilter: 'auto' to dynamically adjust the filter based on speed (EMA smoothing), targeting consistent ~10s intervals regardless of transport mode.
await BackgroundLocation.configure({
distanceFilter: 'auto',
autoDistanceFilter: {
targetInterval: 10, // seconds (default: 10)
minDistance: 10, // meters (default: 10)
maxDistance: 500, // meters (default: 500)
},
// ...other options
});Notification (Android)
Customize the persistent foreground service notification on Android. Has no effect on iOS or Web.
notification: {
title: 'Tracking Active',
text: 'Your location is being recorded',
}API Reference
Complete TypeScript API — methods, events, and interfaces exported from capacitor-bglocation.
Methods
| Method | Returns | Description |
|---|---|---|
checkPermissions() | Promise<LocationPermissionStatus> | Check current location permission status. |
requestPermissions(options?) | Promise<LocationPermissionStatus> | Request location permissions. On Android 10+, request foreground first, then background. |
configure(options) | Promise<ConfigureResult> | Configure tracking parameters. Must be called before start(). Returns license mode. |
start() | Promise<LocationState> | Start background location tracking. Requires configure() and permissions. |
stop() | Promise<LocationState> | Stop background location tracking. |
getState() | Promise<LocationState> | Get current plugin state (enabled, tracking). |
getCurrentPosition(options?) | Promise<Location> | Request a single location update. |
checkBatteryOptimization() | Promise<BatteryWarningEvent> | Check if battery optimization affects tracking (Android only). |
requestBatteryOptimization() | Promise<BatteryWarningEvent> | Open battery optimization settings (Android only). |
removeAllListeners() | Promise<void> | Remove all event listeners. |
Events
Register listeners via BackgroundLocation.addListener(event, handler).
| Event | Payload | Description |
|---|---|---|
onLocation | Location | Location update based on distanceFilter. |
onHeartbeat | HeartbeatEvent | Periodic heartbeat, even when stationary. |
onProviderChange | ProviderChangeEvent | GPS/network provider status changed. |
onHttp | HttpEvent | Native HTTP POST result (requires http config). |
onDebug | DebugEvent | Debug log message (requires debug: true). |
onBatteryWarning | BatteryWarningEvent | Battery optimization detected (Android only). |
onAccuracyWarning | AccuracyWarningEvent | Approximate location granted (iOS 14+ only). |
onMockLocation | MockLocationEvent | Mock location detected (Android only). |
onPermissionRationale | PermissionRationaleEvent | Show rationale before background permission (Android 11+). |
onTrialExpired | TrialExpiredEvent | Trial session ended — tracking auto-stopped. |
onGeofence | GeofenceEvent | Geofence transition (enter, exit, dwell). |
Location Object
Returned by onLocation, getCurrentPosition(), and inside HeartbeatEvent.
interface Location {
latitude: number;
longitude: number;
accuracy: number; // meters
speed: number; // m/s (≥ 0)
heading: number; // degrees 0-360, -1 if unavailable
altitude: number; // meters above sea level
timestamp: number; // ms since epoch
isMoving: boolean; // true when speed > 0.5 m/s
isMock: boolean; // Android only, always false on iOS/Web
effectiveDistanceFilter?: number; // only in 'auto' mode
}Geofencing
Monitor circular regions for enter, exit, and dwell transitions. Up to 20 geofences can be active simultaneously.
| Method | Description |
|---|---|
addGeofence(geofence) | Add a single geofence region. Replaces if identifier exists. |
addGeofences(options) | Add multiple geofences atomically (all-or-nothing). |
removeGeofence(options) | Remove geofence by identifier. No-op if not found. |
removeAllGeofences() | Remove all registered geofences. |
getGeofences() | List currently registered geofences. |
Listen for transitions via the onGeofence event. Geofencing is supported on Android — iOS support is coming soon. Web rejects with UNSUPPORTED.
Geofence Interface
interface Geofence {
identifier: string; // unique ID, used to remove/replace
latitude: number; // center latitude in degrees
longitude: number; // center longitude in degrees
radius: number; // radius in meters (min ~100m recommended)
notifyOnEntry?: boolean; // default: true
notifyOnExit?: boolean; // default: true
notifyOnDwell?: boolean; // default: false
dwellDelay?: number; // seconds before dwell fires (default: 300)
extras?: Record<string, string>; // attached to events
}GeofenceEvent Interface
interface GeofenceEvent {
identifier: string; // which geofence triggered
action: 'enter' | 'exit' | 'dwell';
location: Location | null; // location at time of transition
extras?: Record<string, string>; // extras from geofence definition
timestamp: number; // epoch ms
}Error Codes
| Code | Description |
|---|---|
NOT_CONFIGURED | configure() not called before adding geofences. |
GEOFENCE_LIMIT_EXCEEDED | Max 20 geofences active simultaneously. |
GEOFENCE_ERROR | Native geofencing registration failed. |
UNSUPPORTED | Web platform — geofencing not available. |
Platform Guides
Platform-specific setup for iOS and Android. The plugin handles most configuration automatically via npx cap sync, but some native settings require manual steps.
iOS
Info.plist — Required Keys
Add these usage description keys to your app's Info.plist. Apple rejects apps without them.
<key>NSLocationWhenInUseUsageDescription</key> <string>We need your location to track your route.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>Background location is needed for continuous tracking.</string>
Background Modes
Enable Location updates in Xcode → Signing & Capabilities → Background Modes. This adds to your entitlements:
<key>UIBackgroundModes</key> <array> <string>location</string> </array>
SLC Fallback
The plugin automatically registers for Significant Location Change (SLC) monitoring. If iOS kills the app in background, SLC will relaunch it and resume standard GPS tracking — no action needed on your part.
Minimum Deployment Target
iOS 15.0+ (required by Capacitor 8).
Android
Permissions
The plugin declares all required permissions in its own AndroidManifest.xml. They merge automatically via manifest merger — no manual edits needed:
ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION ACCESS_BACKGROUND_LOCATION <!-- Android 10+ --> FOREGROUND_SERVICE FOREGROUND_SERVICE_LOCATION POST_NOTIFICATIONS <!-- Android 13+ -->
Foreground Service
The plugin runs tracking as a Foreground Service with a persistent notification. This prevents Android from killing the process. In debug mode, the notification shows live coordinates and counters.
Battery Optimization
Many OEMs (Samsung, Xiaomi, Huawei, OnePlus) add aggressive battery killing beyond stock Android. The plugin detects this and emits onBatteryWarning with a dontkillmyapp.com link for the specific device.
Two-Step Permission Flow
Android 10+ requires requesting foreground location first, then background ("Allow all the time") as a separate step. The plugin emits onPermissionRationale before the background prompt so you can show a custom UI explaining why it's needed.
// Step 1: foreground
await BackgroundLocation.requestPermissions({
permissions: ['location'],
});
// Step 2: background (after foreground granted)
await BackgroundLocation.requestPermissions({
permissions: ['backgroundLocation'],
});Minimum SDK
minSdk 26 (Android 8.0), compileSdk 36.
Licensing
How license keys work, trial mode behavior, and offline validation.
Trial Mode
Without a license key, the plugin runs in trial mode — all features are available, but with these restrictions:
- •30-minute sessions — tracking automatically stops after 30 minutes. The
onTrialExpiredevent is emitted. - •1-hour cooldown — after a session expires, you must wait 1 hour before starting another.
- •Debug forced on — verbose logging is always active in trial mode.
Adding a License Key
After purchase, add your license key to capacitor.config.ts:
// capacitor.config.ts
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.yourcompany.app',
plugins: {
BackgroundLocation: {
licenseKey: 'BGL1-eyJ...',
},
},
};
export default config;Then run npx cap sync to apply the key to both platforms.
How Validation Works
- •RSA-2048 cryptographic validation — the key is a signed JWT containing your bundle ID and expiration date.
- •Fully offline — no server calls, no phone-home. The public key is embedded in the plugin binary.
- •Bundle ID bound — the key works only for the specific bundle ID it was generated for. One key covers both iOS and Android (same bundle ID).
- •Annual expiration — keys expire after 365 days. Renewal generates a new key automatically.
License Status
The configure() method returns the license validation result:
const result = await BackgroundLocation.configure({ ... });
// result.licenseMode: 'full' | 'trial'
// result.licenseExpiresAt: '2027-03-14T...' (full mode only)
// result.licenseError: 'EXPIRED' (trial mode only)Examples
Real-world integration patterns for common use cases.
Fleet / Delivery Tracking
Continuous tracking with server-side posting. Use auto distance filter for consistent updates regardless of vehicle speed.
import { BackgroundLocation } from 'capacitor-bglocation';
await BackgroundLocation.configure({
distanceFilter: 'auto',
autoDistanceFilter: {
targetInterval: 10,
minDistance: 10,
maxDistance: 500,
},
desiredAccuracy: 'high',
heartbeatInterval: 30,
locationUpdateInterval: 5000,
fastestLocationUpdateInterval: 2000,
http: {
url: 'https://api.fleet.com/vehicle/location',
headers: { Authorization: 'Bearer <token>' },
buffer: { maxSize: 2000 },
},
notification: {
title: 'Route Active',
text: 'Tracking your delivery route',
},
});
BackgroundLocation.addListener('onHttp', (event) => {
if (!event.success) {
console.warn('Buffered:', event.bufferedCount);
}
});
await BackgroundLocation.start();Fitness / Running App
High-frequency tracking with local processing. Keep locations in memory and sync on demand.
import { BackgroundLocation } from 'capacitor-bglocation';
import type { Location } from 'capacitor-bglocation';
const route: Location[] = [];
await BackgroundLocation.configure({
distanceFilter: 5,
desiredAccuracy: 'high',
heartbeatInterval: 10,
locationUpdateInterval: 3000,
fastestLocationUpdateInterval: 1000,
debug: false,
});
BackgroundLocation.addListener('onLocation', (location) => {
route.push(location);
updateMapPolyline(route);
updateStats(location.speed, route.length);
});
BackgroundLocation.addListener('onHeartbeat', (event) => {
if (event.location) {
updateStationaryIndicator(event.location);
}
});
await BackgroundLocation.start();
// When workout ends:
// await BackgroundLocation.stop();
// saveRoute(route);Geofencing — Points of Interest
Monitor up to 20 circular regions. Works even when the app is terminated.
import { BackgroundLocation } from 'capacitor-bglocation';
// Configure first (required before geofencing)
await BackgroundLocation.configure({
distanceFilter: 50,
desiredAccuracy: 'balanced',
heartbeatInterval: 60,
locationUpdateInterval: 10000,
fastestLocationUpdateInterval: 5000,
});
// Add geofences
await BackgroundLocation.addGeofence({
identifier: 'office',
latitude: 52.2297,
longitude: 21.0122,
radius: 100,
notifyOnEntry: true,
notifyOnExit: true,
});
// Listen for transitions
BackgroundLocation.addListener('onGeofence', (event) => {
console.log(event.identifier, event.action);
// 'office', 'enter' | 'exit' | 'dwell'
if (event.action === 'enter') {
showNotification('Arrived at office');
}
});
await BackgroundLocation.start();