Coming soon — we're finishing final tests before the public release. Explore the docs or check pricing in the meantime.

capacitor-bglocation

Documentation

Everything you need to integrate background location tracking into your Capacitor app.

Quick Install

npm install capacitor-bglocationnpx cap sync

Getting 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

OptionTypeDefaultDescription
distanceFilternumber | 'auto'15Minimum distance in meters between location updates, or 'auto' for speed-adaptive mode.
desiredAccuracystring'high''high' | 'balanced' | 'low'
heartbeatIntervalnumber15Heartbeat interval in seconds. Fires even when stationary.
locationUpdateIntervalnumber5000Update interval in ms. Android only.
debugbooleanfalseEnable 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

MethodReturnsDescription
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).

EventPayloadDescription
onLocationLocationLocation update based on distanceFilter.
onHeartbeatHeartbeatEventPeriodic heartbeat, even when stationary.
onProviderChangeProviderChangeEventGPS/network provider status changed.
onHttpHttpEventNative HTTP POST result (requires http config).
onDebugDebugEventDebug log message (requires debug: true).
onBatteryWarningBatteryWarningEventBattery optimization detected (Android only).
onAccuracyWarningAccuracyWarningEventApproximate location granted (iOS 14+ only).
onMockLocationMockLocationEventMock location detected (Android only).
onPermissionRationalePermissionRationaleEventShow rationale before background permission (Android 11+).
onTrialExpiredTrialExpiredEventTrial session ended — tracking auto-stopped.
onGeofenceGeofenceEventGeofence 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.

MethodDescription
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

CodeDescription
NOT_CONFIGUREDconfigure() not called before adding geofences.
GEOFENCE_LIMIT_EXCEEDEDMax 20 geofences active simultaneously.
GEOFENCE_ERRORNative geofencing registration failed.
UNSUPPORTEDWeb 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 onTrialExpired event 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();