---
title: Announcing the Capacitor Background Geolocation Plugin
description: Our new Capacitor background geolocation plugin records positions into a native SQLite queue and uploads them to your server in batches.
date:
  created: 2026-09-03
  updated: 2026-09-03
authors:
  - robingenz
categories:
  - Announcements
  - Capacitor
  - SDKs
links:
  - Capacitor Background Geolocation: sdks/capacitor/background-geolocation.md
faq: true
---

# Announcing the Capacitor Background Geolocation Plugin

Most background location setups in Capacitor apps share a silent failure mode: the positions are collected in JavaScript, and JavaScript stops running the moment the operating system suspends the web view. The result is a track with holes in it, discovered days later in production. The [Capacitor Background Geolocation plugin](../../sdks/capacitor/background-geolocation.md) is our answer: a Capacitor background geolocation plugin that records every position natively into an SQLite queue and uploads it to your server in batches, whether or not the web view is awake. It's available today to all Capawesome [Insiders](../../insiders/index.md).

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Build and deploy your Capacitor app with Capawesome Cloud" src="https://capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
  </a>
</div>

**Key takeaways:**

- `@capawesome-team/capacitor-background-geolocation` keeps receiving position updates in the background through a correctly plumbed Android 14+ foreground service and the iOS `location` background mode.
- Every position of a watch session can be stored in a native SQLite queue (50,000 positions by default, about 10 MB) that survives app restarts and force-quits.
- The HTTP upload posts queued positions to your server in batches, with at-least-once delivery, exponential backoff, and a status-code contract documented line by line.
- A first-class permissions API mirrors the two-step background location flow that Android 11+ and iOS actually require.
- Devices without Google Play services work too: the plugin falls back to the platform location manager automatically.
- Requires Capacitor 8 or later, Android and iOS only, part of the Capawesome Insiders subscription.

## Why a New Capacitor Background Geolocation Plugin?

Because the hard part of background geolocation is not getting positions, it's not losing them. `watchPosition` callbacks live in the web view, and the web view is exactly what the operating system suspends first when your app leaves the screen. A reliable pipeline has to run natively: the foreground service on Android, the background mode on iOS, and a durable store in between that doesn't care whether JavaScript is currently allowed to execute.

The established commercial option in this space is Transistorsoft's SDK, which bundles an accelerometer-driven motion-detection engine along with its tracking pipeline. Our plugin deliberately takes a different approach, with explicit tuning knobs instead of a state machine and public platform APIs instead of a closed binary. If you're weighing the two, [An Alternative to Transistorsoft Background Geolocation](./alternative-to-transistorsoft-background-geolocation.md) compares them in depth. The short version: this plugin concentrates its engineering on the delivery side, so that what was tracked actually arrives.

## Installation

To install the Capacitor Background Geolocation plugin, please refer to the [Installation](../../sdks/capacitor/background-geolocation.md/#installation) section in the plugin documentation. It's published to the Capawesome npm registry, so installation requires the license key that comes with a [Capawesome Insiders](../../insiders/index.md) subscription.

Two platform prerequisites matter for background use. On Android, declare `ACCESS_BACKGROUND_LOCATION` in your own `AndroidManifest.xml`; the plugin leaves this permission out deliberately, because [Google Play requires a policy declaration and review](https://support.google.com/googleplay/android-developer/answer/9799150){:target="_blank"} for every app that requests it. On iOS, add the two location usage description keys to `Info.plist` and enable the `location` background mode.

## Usage

Here's the shape of a typical integration, from the permission flow to positions landing on your server.

### Request permissions in two steps

Neither platform lets you ask for foreground and background location in one prompt, and the plugin doesn't pretend otherwise. Request `location` first, then `backgroundLocation` in a separate call, ideally after explaining why:

```typescript
import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation';

const requestPermissions = async () => {
  let status = await BackgroundGeolocation.requestPermissions({
    permissions: ['location', 'notifications'],
  });
  if (status.location === 'granted') {
    status = await BackgroundGeolocation.requestPermissions({
      permissions: ['backgroundLocation'],
    });
  }
  return status;
};
```

On Android 11 and later, the second call takes the user to the app's location settings, where *Allow all the time* must be selected. On iOS, it triggers the system prompt to upgrade from *While Using the App* to *Always*. Without the background permission a watch session still works, but position updates may be suspended while the app is in the background. [`openSettings()`](../../sdks/capacitor/background-geolocation.md#opensettings) covers permanently denied permissions.

### Get a one-shot position

Not everything needs a session. [`getCurrentPosition(...)`](../../sdks/capacitor/background-geolocation.md#getcurrentposition) fetches a single position with a configurable accuracy, a timeout, and a maximum age for accepting a cached fix:

```typescript
import { Accuracy, BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation';

const getCurrentPosition = async () => {
  const { position } = await BackgroundGeolocation.getCurrentPosition({
    accuracy: Accuracy.High,
    timeout: 10000,
  });
  return position;
};
```

If no fix arrives within the timeout, the call rejects with the `TIMEOUT` error code. Every position, here and in watch sessions, reports latitude, longitude, accuracy, altitude, bearing, and speed, plus a `simulated` flag on Android that exposes positions delivered by a mock location provider — useful for any app where users have an incentive to fake their location.

### Start a watch session

[`startWatching(...)`](../../sdks/capacitor/background-geolocation.md#startwatching) starts the one active watch session, whether that's a run being recorded, a delivery shift, or a live-shared location, and delivers positions via the [`positionChange`](../../sdks/capacitor/background-geolocation.md#addlistenerpositionchange-) event. On Android this starts a foreground service, which is why the `androidNotification` option is mandatory: every foreground service must show a persistent notification, and the plugin gives you full control over its content instead of inventing one.

```typescript
import { Accuracy, ActivityType, BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation';

const startWatching = async () => {
  await BackgroundGeolocation.addListener('positionChange', (event) => {
    console.log('New position: ', event.position);
  });
  await BackgroundGeolocation.addListener('positionError', (event) => {
    console.error('Position error: ', event.code, event.message);
  });
  await BackgroundGeolocation.startWatching({
    accuracy: Accuracy.High,
    distanceFilter: 10,
    androidInterval: 5000,
    androidNotification: {
      title: 'Location Tracking',
      text: 'Your location is being tracked.',
    },
    iosActivityType: ActivityType.Fitness,
  });
};
```

The options are the real tuning knobs: `accuracy`, a `distanceFilter` in meters (default 10, so a stationary device records nothing), an update interval on Android, and on iOS an `iosActivityType` that helps the operating system schedule updates for the activity at hand, from fitness to automotive navigation. Errors after the session has started, such as the user disabling location services, arrive through the `positionError` event instead of failing silently.

### The queue: nothing gets lost

This is the part that makes the plugin background-proof. Enable the queue with [`setConfig(...)`](../../sdks/capacitor/background-geolocation.md#setconfig) and every position of a watch session is written to a native SQLite database, regardless of what the web view is doing:

```typescript
import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation';

const enableQueue = async () => {
  await BackgroundGeolocation.setConfig({ maxSize: 50000 });
};
```

The `positionChange` event then becomes what it honestly is, a live feed for your UI, while the queue is the durable record. When your app returns to the foreground, drain it: [`getQueuedPositions(...)`](../../sdks/capacitor/background-geolocation.md#getqueuedpositions) reads a page of positions (oldest first, each with a strictly increasing `id`), and [`deleteQueuedPositions(...)`](../../sdks/capacitor/background-geolocation.md#deletequeuedpositions) acknowledges everything up to the last `id` you persisted. Reading and deleting are separate on purpose, so a crash in between never loses a position. The whole drain loop is a handful of lines:

```typescript
import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation';

const drainQueue = async () => {
  let hasMore = true;
  while (hasMore) {
    const result = await BackgroundGeolocation.getQueuedPositions({
      limit: 1000,
    });
    if (!result.positions.length) {
      break;
    }
    await persist(result.positions);
    await BackgroundGeolocation.deleteQueuedPositions({
      upToId: result.positions[result.positions.length - 1].id,
    });
    hasMore = result.hasMore;
  }
};
```

The default `maxSize` of 50,000 positions is about 10 MB on disk and roughly 100 hours of walking at the default distance filter. It's a disaster ceiling rather than a working set; an app that drains regularly stays near zero. [`getQueueStatus()`](../../sdks/capacitor/background-geolocation.md#getqueuestatus) reports `pendingCount`, `droppedCount`, and `lastUploadedAt`, and [`clearQueue()`](../../sdks/capacitor/background-geolocation.md#clearqueue) discards everything, for example on sign-out. One warning worth internalizing early: `setConfig(...)` replaces the whole stored configuration, so always pass every property you want to keep — [`getConfig()`](../../sdks/capacitor/background-geolocation.md#getconfig) returns what you last set, so you can spread it to change a single property.

### Upload positions to your server

If your backend needs the positions rather than the app, add a `url` and the plugin uploads the queue in batches without involving JavaScript at all:

```typescript
import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation';

const enableUpload = async () => {
  await BackgroundGeolocation.addListener('uploadFailed', (event) => {
    console.error('Upload failed: ', event.statusCode, event.message);
  });
  await BackgroundGeolocation.setConfig({
    url: 'https://api.example.com/positions',
    batchSize: 100,
    flushInterval: 60000,
    headers: {
      Authorization: 'Bearer eyJhbGciOi...',
    },
    extras: {
      userId: 'abc',
    },
  });
};
```

Each upload is an HTTP `POST` carrying a batch of positions and whatever static `extras` you configured:

```json
{
  "positions": [
    {
      "id": 4711,
      "latitude": 52.52,
      "longitude": 13.405,
      "accuracy": 5,
      "altitude": null,
      "altitudeAccuracy": null,
      "bearing": null,
      "speed": null,
      "simulated": false,
      "timestamp": 1723291200000
    }
  ],
  "extras": { "userId": "abc" }
}
```

Delivery is at least once, and every position carries its queue `id`, so your endpoint deduplicates by `id` per device and delivery becomes effectively exactly-once. The response body is ignored; only the status code matters. A `2xx` acknowledges the batch. `401`, `408`, `429`, `5xx`, timeouts, and network errors keep it queued for a retry with exponential backoff (`401` is retryable so you can rotate an expired token via `setConfig(...)` without losing anything). **Any other status code drops the batch permanently**, because a rejected batch must never block the head of the queue, so answer with a retryable `503` while your server can't accept data.

Batching is also the battery-friendly choice: every upload wakes the cellular radio, so a larger `batchSize` and a longer `flushInterval` let many positions share one wake-up. Set `batchSize: 1` only if every position must arrive immediately. And you don't need a finished backend to try any of this: the free [Background Geolocation Playground](https://background-geolocation-playground.capawesome.io){:target="_blank"} is a ready-to-use upload target that shows arriving positions in a table and on a map.

## What Works in the Background, and What Doesn't

With the background permission granted, a watch session keeps delivering and queueing positions while the app is in the background, on both platforms. What ends the session is a force-quit: when the user terminates the app, tracking stops, and the plugin treats that as the operating system restriction it is instead of working around it with private APIs. The queued positions survive and are uploaded the next time the app runs. One honest limitation follows from this: the queue only fills while a watch session is active, so the stretch between a force-quit and the next start stays a gap that nothing can backfill.

If your app must react to movement even after termination, that capability exists in one place only: operating-system-managed region monitoring, which can relaunch a terminated app. That's the job of the [Capacitor Geofences plugin](../../sdks/capacitor/geofences.md), and the two are designed to run side by side — geofences as the always-on layer, a watch session for the detailed trail once the app is running.

## Tuning Battery Without a Black Box

The plugin deliberately ships no motion-detection state machine that toggles the GPS based on accelerometer data. Instead, battery consumption is controlled by knobs whose behavior you can predict: a lower `accuracy`, a larger `distanceFilter`, a longer `androidInterval`. On iOS, `iosPausesAutomatically` additionally lets the operating system pause updates while the device is stationary, with a documented trade-off: iOS alone decides when to resume, so a session can stay silent for a while and [`isWatching()`](../../sdks/capacitor/background-geolocation.md#iswatching) still reports `true`.

Two more platform realities the plugin handles rather than hides: on devices without Google Play services (certain Huawei models, many kiosk devices), it falls back to the platform location manager automatically, and on iOS, users who granted reduced accuracy can be asked for session-scoped full accuracy via [`requestTemporaryFullAccuracy(...)`](../../sdks/capacitor/background-geolocation.md#requesttemporaryfullaccuracy).

## FAQ

### Does Capacitor support background location tracking?

Yes, with the right plugin. The official `@capacitor/geolocation` plugin covers foreground use, while background tracking needs an Android foreground service, the iOS `location` background mode, and native persistence. The [Capacitor Background Geolocation plugin](../../sdks/capacitor/background-geolocation.md) provides all three, including an SQLite queue and a native HTTP upload.

### Does tracking continue after the user force-quits the app?

No. The watch session ends on both platforms when the app is terminated; that's an operating system restriction. Queued positions are preserved and uploaded on the next start, and the [Capacitor Geofences plugin](../../sdks/capacitor/geofences.md) covers reacting to movement while the app stays terminated.

### Why is the `androidNotification` option required?

Background position updates on Android require a foreground service, and every foreground service must display a persistent notification. The plugin builds it from your options, so you control the title, text, icon, and color.

### Does the plugin work on devices without Google Play services?

Yes. It falls back to the platform location manager automatically, and `androidForceLocationManager` forces that behavior on devices where the fused provider is unreliable.

### Is the Capacitor Background Geolocation plugin free?

No. It's part of the Capawesome [Insiders](../../insiders/index.md) subscription, which also covers all other Insiders plugins and priority support.

### Does it work with Ionic, Angular, React, or Vue?

Yes. The plugin is framework-agnostic and works in any Capacitor app, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects.

## Availability

The Capacitor Background Geolocation plugin is available today to all Capawesome [Insiders](../../insiders/index.md) and requires Capacitor 8 or later. It supports Android and iOS; there is no web implementation. The same subscription covers every other Insiders plugin, so there's no separate license to buy.

New plugins and notable releases are announced in the Capawesome newsletter first.

[Subscribe to the Capawesome Newsletter](https://capawesome.io/newsletter/){ .md-button .md-button--primary }

## Conclusion

Collecting positions was never the hard part of background geolocation in a Capacitor app. The hard part is a pipeline that behaves when the web view sleeps, the network drops, the token expires, and the user swipes the app away, all in the same afternoon. That pipeline, from the foreground service to the acknowledged upload, is what the [Capacitor Background Geolocation plugin](../../sdks/capacitor/background-geolocation.md) absorbs.

**Further reading:**

- [An Alternative to Transistorsoft Background Geolocation](./alternative-to-transistorsoft-background-geolocation.md) — how this plugin compares to the commercial incumbent, axis by axis
- [API Reference](../../sdks/capacitor/background-geolocation.md#api) — every method, option, event, and error code

**Missing a feature?** [Create a feature request](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose){:target="_blank"} in our GitHub repository.

If you have any questions, join us on the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"}. To stay updated on the latest news, subscribe to the [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"}.
