Most background location setups in Capacitor apps share a silent failure mode: positions are collected in JavaScript, and JavaScript stops the moment the operating system suspends the web view. The result is a track with holes, discovered in production. We just released the Capacitor Background Geolocation plugin to fix exactly that: every position is recorded natively into an SQLite queue and uploaded to your server, whether or not the web view is awake.
Permissions the way the platforms actually work
Neither platform lets you ask for foreground and background location in one prompt, so the plugin doesn't pretend otherwise:
import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation';
let status = await BackgroundGeolocation.requestPermissions({
permissions: ['location', 'notifications'],
});
if (status.location === 'granted') {
status = await BackgroundGeolocation.requestPermissions({
permissions: ['backgroundLocation'],
});
}
Watch sessions with real tuning knobs
import { Accuracy, BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation';
await BackgroundGeolocation.startWatching({
accuracy: Accuracy.High,
distanceFilter: 10,
androidNotification: {
title: 'Location Tracking',
text: 'Your location is being tracked.',
},
});
androidNotification is mandatory because Android requires a foreground service with a persistent notification — the plugin makes that explicit instead of inventing one. There's deliberately no accelerometer-driven motion-detection state machine; battery is controlled through predictable knobs (accuracy, distanceFilter, androidInterval, iosPausesAutomatically).
The queue is the source of truth
Enable it once and every position is written natively:
await BackgroundGeolocation.setConfig({ maxSize: 50000 });
The positionChange event becomes what it honestly is — a live feed for your UI — while the queue survives restarts and force-quits. Drain it with getQueuedPositions(...) and acknowledge with deleteQueuedPositions({ upToId }); ids are strictly increasing, so a crash between reading and persisting never loses a position.
Upload without JavaScript
Add a url and the queue uploads itself in batches:
await BackgroundGeolocation.setConfig({
url: 'https://api.example.com/positions',
batchSize: 100,
flushInterval: 60000,
headers: { Authorization: 'Bearer ...' },
});
Delivery is at least once with a documented status-code contract: 2xx acknowledges, 401/408/429/5xx retry with exponential backoff, anything else drops the batch permanently so it can't block the queue. Deduplicate by position id on the server and delivery is effectively exactly-once. You can test the whole pipeline against the free Background Geolocation Playground before your endpoint exists.
One honest limit: a force-quit ends the watch session — that's an OS restriction. Queued positions survive and upload on the next start, and OS-managed geofencing (a separate plugin) covers relaunch scenarios.
The plugin is part of Capawesome Insiders and requires Capacitor 8+. The full announcement covers the complete server contract and the battery trade-offs.
Top comments (0)