Skip to content

FAQ

Distilled from 11 590 support emails (2015–2026).


Licensing & Pricing

Do I need a license for iOS?

Yes. Both iOS and Android require a license key. One license purchase covers both platforms — you generate a separate key for each from the Customer Dashboard. The Android key is bound to your applicationId; the iOS key is bound to your bundleIdentifier. There is no extra charge — both keys are included in a single tier purchase.


Does one application key work for both Android and iOS?

One license purchase covers both platforms, but you generate separate keys for iOS and Android from the Customer Dashboard. Enter the Android applicationId (e.g. com.mycompany.myapp) when creating the Android key, and the iOS bundleIdentifier (e.g. com.mycompany.myapp) when creating the iOS key.


Is the license a one-time purchase, or do I pay every year?

Purchased keys unlock the plugin forever — there is no recurring charge for the key to keep working. The optional yearly support subscription ($199/year) provides priority tech support and access to the latest releases. If your support subscription lapses, your key and the version you have installed continue to work, but you cannot install newer releases until you renew.


What does "1 year access to latest updates" mean?

It means your purchase includes one year of priority support responses and access to the latest releases. After one year, your key and the plugin version you have installed continue to work — but if a new release is published after your subscription lapses, you cannot install it until you renew. Renew via the Customer Dashboard to restore access.


What are the license plans?

Plan Price Keys When to use
Starter $399 1 One app
Venture $599 5 Up to 5 distinct apps
Pro $749 25 Up to 25 distinct apps
Studio $999 100 Up to 100 distinct apps

Ask yourself: "How many distinct Android applicationIds will I publish to the Play Store?" That is the number of keys you need. The number of users per app is unlimited.


Can I upgrade from Starter to Venture later?

Yes. Log in to the Customer Dashboard and click the Upgrade button. The cost is $250 (Starter → Venture). You save $50 by purchasing Venture outright rather than upgrading after the fact. The upgrade price is calculated from the non-sale (strikethrough) price.


Is there a free trial key or a discount code?

There are no discount codes. Trial keys are available upon request — email info@transistorsoft.com. The plugin is also fully functional in debug builds without any key, despite the validation warning messages. Integrate, field-test, and satisfy yourself completely before purchasing.


Are licenses per-user or per-install?

Neither. There is no limit on the number of users or device installs. Each key is bound to one app identifier (applicationId on Android, bundleIdentifier on iOS) for unlimited users and installs. The plugin does not contact Transistor Software servers to validate keys at runtime.


Can I use my key in development builds with a different applicationId suffix?

Yes. License keys automatically allow a set of common development suffixes on the base applicationId:

.dev · .development · .staging · .stage · .qa · .uat · .test · .debug

So a key generated for com.mycompany.myapp also validates for com.mycompany.myapp.debug, com.mycompany.myapp.staging, etc. — without generating additional keys. Custom suffixes beyond this list are available upon request at info@transistorsoft.com — include your order number.


Can I use a Cordova license with the Capacitor plugin, or a React Native license with Flutter?

No. Each framework has its own plugin and its own license. Licenses are not transferable between frameworks. If you are migrating from Cordova to Capacitor, you need a new Capacitor license.


What is cordova-background-geolocation-lt?

It is the npm package name for the Transistor Software Cordova plugin. The -lt suffix is historical; there is no meaningful difference from cordova-background-geolocation. It is the same premium plugin.


Does the Capacitor plugin require a separate license from Cordova?

Yes. A Cordova key is not valid for the Capacitor plugin. Purchase a Capacitor license separately.


Can I use the plugin in an Ionic or Angular app?

Yes. The Capacitor or Cordova plugin works with any Ionic/Angular project built on those platforms. The plugin does not care which JavaScript UI framework you use.


What is the Firebase adapter? Do I need it to upload locations?

The Firebase adapter is an optional paid add-on that writes location records directly into Firebase Firestore. You do not need it to upload locations — the plugin has a built-in HTTP service that posts to any REST endpoint. The adapter is only useful if you specifically want to target Firestore. It requires a Background Geolocation license for your platform in addition to the adapter license. It supports Firestore only — not the Realtime Database.


Does polygon geofencing cost extra?

Yes. Polygon geofencing is a separate purchasable add-on. Circular geofencing is included with every Background Geolocation license at no extra cost.


Getting Started

Can I try the plugin before buying?

Yes. The plugin is fully functional in debug builds without a license key. Build in debug mode, go outside for a real-world walk of at least 1 km with logger: { debug: true } enabled, and confirm it meets your needs before purchasing.


Where is the demo app?

Each platform's GitHub repository contains a /example folder with a ready-to-run demo app:

Clone the repo, follow the README setup instructions, and install it to a device.


How do I get my license key after purchasing?

After purchase you receive two emails. The second email contains registration instructions and a link to the Customer Dashboard. Register your order there and click Create License Key — once for Android (entering your applicationId) and once for iOS (entering your bundleIdentifier). You self-generate both keys; they are not sent to you automatically.


What are my Android applicationId and iOS bundleIdentifier?

Android — the value of applicationId in android/app/build.gradle, for example com.mycompany.myapp.

iOS — the value of PRODUCT_BUNDLE_IDENTIFIER in Xcode (or bundleIdentifier in app.json for React Native / Expo), typically the same value as your Android applicationId.

These are what you enter when generating license keys in the Customer Dashboard — one key per platform.


Can I use the plugin with Expo?

Yes — the plugin is fully supported across all Expo workflows: Expo Go, Expo Dev Client, managed workflow, and bare workflow. Follow the Expo Setup Instructions linked in the GitHub repo README.


The build fails with Could not find any matches for com.transistorsoft:tslocationmanager:+.

The tslocationmanager library is published to Maven Central — there is no custom repository URL required. If Gradle cannot resolve it, the problem is in your project's repository configuration, not on Transistor Software's servers. Check that mavenCentral() is present in your root android/build.gradle (or settings.gradle) repositories block:

allprojects {
    repositories {
        google()
        mavenCentral()  // ← required
    }
}

If mavenCentral() is already there and the build still fails, your build environment cannot reach Maven Central — a network or proxy issue that affects all Android dependencies, not just this library.


Tracking Behaviour

Does the plugin track when the app is killed or terminated?

Yes, with stopOnTerminate: false — but the behaviour differs by platform.

iOS: When the app is terminated, the SDK registers a stationary geofence of stationaryRadius metres (default 150 m) around the last known position. When the device moves approximately 200 m, iOS silently relaunches the app in the background and tracking resumes automatically. This is an OS-level mechanism.

Android: The native background service keeps running after the app is killed. The JS/UI process is gone, but the service continues recording and uploading locations. Configure http.url so the service can upload while running headlessly. Enable enableHeadless: true only if you need to run custom JavaScript logic in the headless state (for example, posting a local notification); it is not required for location recording and uploading.

const state = await BackgroundGeolocation.ready({
  app: {
    stopOnTerminate: false,
    startOnBoot: true,
    enableHeadless: true,  // Android only — only needed for custom headless JS logic
  },
  http: {
    url: 'https://your.server.com/api/locations',
    autoSync: true,
  },
});
bg.State state = await bg.BackgroundGeolocation.ready(bg.Config(
  app: bg.AppConfig(
    stopOnTerminate: false,
    startOnBoot: true,
    enableHeadless: true,  // Android only
  ),
  http: bg.HttpConfig(
    url: 'https://your.server.com/api/locations',
    autoSync: true,
  ),
));
// enableHeadless is Android-only; not applicable on iOS
let state = try await BGGeo.shared.ready { config in
    config.app.stopOnTerminate = false
    config.app.startOnBoot = true
    config.http.url = "https://your.server.com/api/locations"
    config.http.autoSync = true
}
val state = BGGeo.instance.ready {
    app.stopOnTerminate = false
    app.startOnBoot = true
    app.enableHeadless = true  // only needed for custom headless logic
    http.url = "https://your.server.com/api/locations"
    http.autoSync = true
}

Does the plugin restart tracking after a device reboot?

Yes, set startOnBoot: true. On Android this registers a BOOT_COMPLETED broadcast receiver. On iOS, the OS resumes monitoring the stationary geofence registered at termination, which relaunches the app and restarts tracking when the device next moves.


Why does tracking stop when the device is stationary?

This is by design — the Philosophy of Operation. The SDK is motion-driven: it only requests location updates when the device is moving. When the device stops, the SDK transitions to the stationary state and stops firing location events. This saves battery significantly. Use stopTimeout (in minutes) to control how long the device must be stationary before the SDK confirms the stopped state. For periodic locations while stationary, use the companion background-fetch plugin (already included as a dependency) and call BackgroundGeolocation.getCurrentPosition() in its callback.


Tracking stops after brief stops during a trip.

Increase stopTimeout to give the SDK more time before declaring the device stopped. The default is 5 minutes. For vehicle use cases, stopTimeout: 10 or higher is common. You can also configure activity.stopDetectionDelay to introduce a delay before stop-detection evaluates.

const state = await BackgroundGeolocation.ready({
  geolocation: {
    distanceFilter: 50,
    stopTimeout: 10,
  },
  activity: {
    stopDetectionDelay: 1,
  },
});
bg.State state = await bg.BackgroundGeolocation.ready(bg.Config(
  geolocation: bg.GeoConfig(
    distanceFilter: 50.0,
    stopTimeout: 10,
  ),
  activity: bg.ActivityConfig(
    stopDetectionDelay: 1,
  ),
));
let state = try await BGGeo.shared.ready { config in
    config.geolocation.distanceFilter = 50
    config.geolocation.stopTimeout = 10
    config.activity.stopDetectionDelay = 1
}
val state = BGGeo.instance.ready {
    geolocation.distanceFilter = 50f
    geolocation.stopTimeout = 10
    activity.stopDetectionDelay = 1
}

Can I get a location update every N minutes on a fixed schedule?

The SDK is event-driven, not interval-based. For periodic locations on a fixed schedule (including while stationary), use the background-fetch plugin (already a dependency) and call BackgroundGeolocation.getCurrentPosition() in the fetch callback. iOS enforces a minimum background-fetch interval of approximately 15 minutes.


Does the plugin work when the screen is locked?

Yes. The plugin runs as a background service and is unaffected by screen state.


Configuration

How do I configure the plugin?

Call BackgroundGeolocation.ready() once per app launch before calling start(). Use the compound config API with grouped sub-configs:

import BackgroundGeolocation, { DesiredAccuracy } from 'react-native-background-geolocation';

const state = await BackgroundGeolocation.ready({
  geolocation: {
    desiredAccuracy: DesiredAccuracy.High,
    distanceFilter: 50,
    stopTimeout: 5,
  },
  http: {
    url: 'https://your.server.com/api/locations',
    autoSync: true,
  },
  app: {
    stopOnTerminate: false,
    startOnBoot: true,
  },
  logger: {
    debug: true,  // disable in production
  },
});

if (!state.enabled) {
  BackgroundGeolocation.start();
}
import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg;

bg.State state = await bg.BackgroundGeolocation.ready(bg.Config(
  geolocation: bg.GeoConfig(
    desiredAccuracy: bg.DesiredAccuracy.high,
    distanceFilter: 50.0,
    stopTimeout: 5,
  ),
  app: bg.AppConfig(
    stopOnTerminate: false,
    startOnBoot: true,
  ),
  http: bg.HttpConfig(
    url: 'https://your.server.com/api/locations',
    autoSync: true,
  ),
));

if (!state.enabled) {
  bg.BackgroundGeolocation.start();
}
import TSLocationManager

let state = try await BGGeo.shared.ready { config in
    config.geolocation.desiredAccuracy = kCLLocationAccuracyBest
    config.geolocation.distanceFilter = 50
    config.geolocation.stopTimeout = 5
    config.http.url = "https://your.server.com/api/locations"
    config.http.autoSync = true
    config.app.stopOnTerminate = false
    config.app.startOnBoot = true
    config.logger.debug = true  // disable in production
}

if !state.enabled {
    try await BGGeo.shared.start()
}
import com.transistorsoft.locationmanager.kotlin.BGGeo

val state = BGGeo.instance.ready {
    geolocation.desiredAccuracy = DesiredAccuracy.HIGH
    geolocation.distanceFilter = 50f
    geolocation.stopTimeout = 5
    http.url = "https://your.server.com/api/locations"
    http.autoSync = true
    app.stopOnTerminate = false
    app.startOnBoot = true
    logger.debug = true  // disable in production
}

if (!state.enabled) {
    BGGeo.instance.start()
}

What does distanceFilter do?

distanceFilter (in metres) is the minimum displacement required before a new location sample is delivered while the device is in moving mode. The SDK automatically scales this value upward at higher speeds (elasticityMultiplier) to reduce the update rate when driving fast. Set disableElasticity: true to disable the speed-based scaling.


What does desiredAccuracy do?

It sets the requested location accuracy and power trade-off. Use DesiredAccuracy.High (GPS-grade) for walking and cycling; lower accuracy values reduce battery consumption for vehicle tracking. Indoors, GPS does not work — location comes from Wi-Fi and cell towers at 40–100 m accuracy regardless of this setting.


How do I stop and restart tracking programmatically?

// Stop tracking
await BackgroundGeolocation.stop();

// Resume tracking
await BackgroundGeolocation.start();

// Force moving state immediately (skips the stationary waiting period)
await BackgroundGeolocation.changePace(true);
// Stop tracking
await bg.BackgroundGeolocation.stop();

// Resume tracking
await bg.BackgroundGeolocation.start();

// Force moving state immediately (skips the stationary waiting period)
await bg.BackgroundGeolocation.changePace(true);
// Stop tracking
try await BGGeo.shared.stop()

// Resume tracking
try await BGGeo.shared.start()

// Force moving state immediately (skips the stationary waiting period)
BGGeo.shared.changePace(true)
// Stop tracking
BGGeo.instance.stop()

// Resume tracking
BGGeo.instance.start()

// Force moving state immediately (skips the stationary waiting period)
BGGeo.instance.changePace(true)

How do I send custom data fields with each location?

Use http.params for static key/value pairs added to every request, or persistence.extras for per-location payload fields:

await BackgroundGeolocation.ready({
  http: {
    url: 'https://your.server.com/api/locations',
    params: { user_id: '123', trip_id: 'abc' },
    headers: { Authorization: 'Bearer mytoken' },
    autoSync: true,
  },
  persistence: {
    extras: { vehicle_id: 'truck-42' },
  },
});
await bg.BackgroundGeolocation.ready(bg.Config(
  http: bg.HttpConfig(
    url: 'https://your.server.com/api/locations',
    params: {'user_id': '123', 'trip_id': 'abc'},
    headers: {'Authorization': 'Bearer mytoken'},
    autoSync: true,
  ),
  persistence: bg.PersistenceConfig(
    extras: {'vehicle_id': 'truck-42'},
  ),
));
try await BGGeo.shared.ready { config in
    config.http.url = "https://your.server.com/api/locations"
    config.http.params = ["user_id": "123", "trip_id": "abc"]
    config.http.headers = ["Authorization": "Bearer mytoken"]
    config.http.autoSync = true
    config.persistence.extras = ["vehicle_id": "truck-42"]
}
BGGeo.instance.ready {
    http.url = "https://your.server.com/api/locations"
    http.params = mapOf("user_id" to "123", "trip_id" to "abc")
    http.headers = mapOf("Authorization" to "Bearer mytoken")
    http.autoSync = true
    persistence.extras = mapOf("vehicle_id" to "truck-42")
}

What does debug: true do and how do I turn off the sounds?

Setting logger: { debug: true } enables audible sound effects that signal each state transition (moving, stationary, location recorded, HTTP success/failure). It is the fastest way to understand what the plugin is doing during a field test. Set logger: { debug: false } (the default) to silence the sounds. Always disable debug mode in production.


iOS Specifics

After terminating the iOS app, tracking doesn't resume until I walk about 200 m. Is that normal?

Yes. This is expected behaviour. When the app terminates with stopOnTerminate: false, the SDK registers a stationary geofence of stationaryRadius metres (default 150 m) around the last known position. iOS monitors that OS-level geofence even while the app is not running. When the device exits the geofence, iOS silently relaunches the app in the background and tracking resumes.

Note that 200 m is typical in urban environments but is not guaranteed — the exact distance is entirely at the discretion of the OS. When your app is relaunched, and how far you travel before that happens, is influenced by the surrounding network environment: the presence and density of Wi-Fi signals, the spacing of cell towers, and geographical obstructions all play a role. This is an iOS platform constraint, not a plugin limitation.


Do I need "Always" location permission for background tracking?

Yes, for fully automatic background tracking. With "Always" permission the SDK manages the motion state machine autonomously, including relaunching after termination.

With "When In Use" permission the SDK can still track — similar to a fitness app with a [Start Workout] button. Call changePace(true) while the app is in the foreground to begin a session. As the user moves the app to the background, iOS allows location updates to continue. When the device becomes stationary and GeoConfig.stopTimeout expires, the SDK calls changePace(false) on itself and stops tracking. The session cannot restart automatically — the user must return to the foreground to begin a new one.


onLocation events stop firing on iOS when the app is in the background.

Make sure BackgroundGeolocation.ready() is called unconditionally at app launch — not inside a UI component, a tab screen, or behind a conditional. When iOS relaunches your app in the background after a stationary geofence triggers, ready() must be reached in that launch path for tracking to resume correctly.


Should I use watchPosition for continuous background tracking?

No. watchPosition is designed for short-duration foreground use (for example, a "show my current position" feature). Do not use it for ongoing background tracking. Call start() with "Always" permission for background tracking.


Android Specifics

Does the foreground service notification appear immediately when I call start()?

Not exactly. At startup the SDK briefly activates a foreground service to fetch the initial motionchange position — a notification appears during this time. Once the SDK confirms the device is stationary, it turns off location services and the foreground service terminates (notification disappears). After that, the notification reappears whenever the SDK is actively recording locations (i.e. while in the moving state) and disappears again when the device stops.


Android tracking stops on Samsung / Huawei / Xiaomi devices.

Some OEM Android builds (Samsung One UI, Huawei EMUI, Xiaomi MIUI) aggressively kill background processes. The plugin correctly sets up a foreground service, but the OEM battery optimisation layer can override it. Instruct users to add your app to the battery whitelist or exclusion list in device settings. This is an OEM issue; see dontkillmyapp.com for per-manufacturer instructions.


What Android permissions does the plugin require?

The plugin's Gradle configuration automatically adds all required permissions to your AndroidManifest.xml. You do not need to add them manually. They include ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION, FOREGROUND_SERVICE, FOREGROUND_SERVICE_LOCATION, ACTIVITY_RECOGNITION, and RECEIVE_BOOT_COMPLETED.


Can I remove ACCESS_BACKGROUND_LOCATION if I only track while the app is in the foreground?

Yes, if your app never initiates background location tracking. Remove the permission from your merged manifest and verify in testing that the plugin behaves correctly for your specific use case before publishing.


Does the plugin work on Android 10 and later?

Yes. Android 10 and later are fully supported and regularly tested. If you see a specific problem on a particular device or Android version, post a GitHub issue with plugin logs from BackgroundGeolocation.logger.emailLog().


Publishing Rejections

My app was rejected by the App Store or Play Store because of background location.

Both stores manually review apps that request background location permission. Reviewers look at what the app actually does — not just what permission it requests.

Rejected use cases — background location (or a foreground service) used to keep the app alive for a purpose unrelated to location:

  • Alarm clock or timer apps using a foreground service to prevent process termination
  • Music or media players requesting background location to stay alive
  • Any app where location is not the core feature visible to the end user

Accepted use cases — apps where continuous location tracking is the primary feature and clearly disclosed to the user:

  • Exercise and fitness tracking (running, cycling, hiking)
  • Delivery and logistics (food delivery, courier, fleet tracking)
  • Ride-sharing and taxi
  • Field service and workforce management
  • Asset and vehicle tracking

If your app was rejected, review the rejection reason carefully. Reviewers often request a demo video or a reviewer account showing the location feature in action. Make sure your App Store / Play Store listing, privacy policy, and permission usage descriptions all clearly state that location is collected, why, and how it is used.

See also: Required declarations for "Foreground Services" and "Health"


Google Play Store declarations for targetSdkVersion 34 (Android 14)

With targetSdkVersion 34, Google Play requires declarations for two permissions the SDK automatically inserts into your AndroidManifest.xml. See the full walkthrough on Medium.

ACTIVITY_RECOGNITION

The SDK uses this permission to monitor the Android Motion API and automatically toggle location tracking ON/OFF based on detected motion (walking, in-vehicle, still, etc.). In the Play Console declaration, select category "Other" and use this text:

The app uses the ActivityTransition API to automatically toggle location-services ON when the device is detected to be in-motion. When the Motion API reports the device is "still", the app turns OFF location-services to conserve battery. This is a documented use-case for the Activity Transition API according to https://developer.android.com/develop/sensors-and-location/location/transitions

Use the same text again when prompted for the Activity recognition permission description.

Without ACTIVITY_RECOGNITION, the SDK falls back to location-only motion detection — requiring 200–1000 m of movement before tracking activates. If you want to remove it intentionally, add this to your AndroidManifest.xml and set disableMotionActivityUpdates: true in your config:

<manifest>
  <uses-permission
    android:name="android.permission.ACTIVITY_RECOGNITION"
    tools:node="remove" />
</manifest>

FOREGROUND_SERVICE_LOCATION

The SDK uses this permission to run a foreground service that continuously monitors device location regardless of whether the app is terminated or the device rebooted. In the Play Console declaration, enable both "User-initiated location sharing" and "Geofencing". A short screen-recording of the app's location feature in action is sufficient for review — the Transistor Software demo app was approved with these declarations.


Geofencing

Do geofences fire when the app is terminated?

Yes. Both iOS and Android monitor geofences at the OS level, independent of whether the app is running. The SDK registers geofences with the native platform APIs, so ENTER/EXIT transitions fire even when the app is killed, provided the device has "Always" location permission.


Why am I receiving multiple duplicate EXIT events?

Android geofencing can produce spurious EXIT events, especially in poor GPS conditions or near a geofence boundary. The SDK includes a trigger-validation gate that confirms EXIT events with a follow-up location before firing them. If you still see duplicates, verify you have not registered the same identifier multiple times and that you are on the latest plugin version. Post a GitHub issue with logs if the problem persists.


Does a geofence fire an ENTER event if the device is already inside when monitoring starts?

Yes, by default (geofenceInitialTriggerEntry: true). To suppress the immediate ENTER event when the device is already inside the fence at the time monitoring begins, set geofenceInitialTriggerEntry: false in your config.


What is the minimum reliable geofence radius?

200 m or larger produces reliable results. Geofencing combines GPS, Wi-Fi, and cell towers. In areas without Wi-Fi, only cell tower changes trigger re-evaluation, making radii below 150–200 m unreliable in rural environments.


How many geofences can I register?

Unlimited. The SDK has a purpose-built system for monitoring infinite geofences beyond what the OS natively supports. iOS allows ~20 simultaneously monitored geofences; Android allows ~100. When your total exceeds the OS maximum, the SDK performs periodic geospatial queries against its SQLite geofence database, activating those within GeoConfig.geofenceProximityRadius of the current position and swapping them in and out as the device moves.


Is polygon geofencing supported?

Yes, as an optional paid add-on (purchased separately). The system uses a two-stage algorithm: a minimum enclosing circle for coarse detection, then native C++ point-in-polygon checks for fine-grained precision. For large polygons in rural areas with sparse Wi-Fi, consider using multiple smaller circular geofences arranged at key boundaries instead.


Data & HTTP

How does the plugin send locations to my server?

The SDK uses a built-in HTTP service backed by a SQLite persist-first queue. A location is written to SQLite first, then the HTTP service uploads it to your configured url. On network failure, records stay in the queue and are retried automatically when connectivity restores. The plugin was originally built for disaster response where cellular networks are assumed unreliable.

await BackgroundGeolocation.ready({
  http: {
    url: 'https://your.server.com/api/locations',
    autoSync: true,
    batchSync: false,       // true = multiple records per request
    headers: { Authorization: 'Bearer yourtoken' },
    method: 'POST',
  },
});
await bg.BackgroundGeolocation.ready(bg.Config(
  http: bg.HttpConfig(
    url: 'https://your.server.com/api/locations',
    autoSync: true,
    batchSync: false,       // true = multiple records per request
    headers: {'Authorization': 'Bearer yourtoken'},
    method: 'POST',
  ),
));
try await BGGeo.shared.ready { config in
    config.http.url = "https://your.server.com/api/locations"
    config.http.autoSync = true
    config.http.batchSync = false   // true = multiple records per request
    config.http.headers = ["Authorization": "Bearer yourtoken"]
    config.http.method = .post
}
BGGeo.instance.ready {
    http.url = "https://your.server.com/api/locations"
    http.autoSync = true
    http.batchSync = false          // true = multiple records per request
    http.headers = mapOf("Authorization" to "Bearer yourtoken")
    http.method = HttpMethod.POST
}

My server receives 400 Bad Request errors.

The error originates at your server, not the plugin. The plugin posts a JSON body with a location root property by default. Check your server logs, verify the expected JSON schema, and use http.rootProperty to change the root key name, or persistence.locationTemplate to customise the payload structure. Subscribe to onHttp to inspect the raw server response from the client side.


I changed locationTemplate but old 400 errors keep retrying.

The SDK encodes the JSON payload at the moment each location is recorded — it does not reformat existing records in SQLite when you change locationTemplate. New locations post correctly with the updated template, but previously recorded locations with the old format remain in the queue and keep retrying until LoggerConfig.maxDaysToPersist expires them (default 3 days). If your server is now returning 200 for new records but old ones keep failing, clear the queue immediately:

await BackgroundGeolocation.destroyLocations();
await bg.BackgroundGeolocation.destroyLocations();
try await BGGeo.shared.dataStore.destroyLocations()
BGGeo.instance.dataStore.destroyLocations()

Can I send only latitude and longitude instead of the full location object?

Yes. Use persistence.locationTemplate to control the exact JSON structure posted per location:

await BackgroundGeolocation.ready({
  http: {
    url: 'https://your.server.com/api/locations',
    autoSync: true,
  },
  persistence: {
    locationTemplate: '{"lat":<%= latitude %>,"lng":<%= longitude %>,"ts":"<%= timestamp %>"}',
  },
});
await bg.BackgroundGeolocation.ready(bg.Config(
  http: bg.HttpConfig(
    url: 'https://your.server.com/api/locations',
    autoSync: true,
  ),
  persistence: bg.PersistenceConfig(
    locationTemplate: '{"lat":<%= latitude %>,"lng":<%= longitude %>,"ts":"<%= timestamp %>"}',
  ),
));
try await BGGeo.shared.ready { config in
    config.http.url = "https://your.server.com/api/locations"
    config.http.autoSync = true
    config.persistence.locationTemplate = #"{"lat":<%= latitude %>,"lng":<%= longitude %>,"ts":"<%= timestamp %>"}"#
}
BGGeo.instance.ready {
    http.url = "https://your.server.com/api/locations"
    http.autoSync = true
    persistence.locationTemplate = """{"lat":<%= latitude %>,"lng":<%= longitude %>,"ts":"<%= timestamp %>"}"""
}

Does the plugin work offline?

Yes. All locations are persisted to SQLite before any upload attempt. If the device is offline, records accumulate locally and upload automatically once connectivity is restored. The plugin handles this transparently with no extra configuration.


What is the uuid field on each location?

uuid is a client-generated identifier attached to every recorded location. It lets you match a location received at your server with the corresponding entry in the plugin's debug logs. It has no other operational significance.


Can I use Firebase Firestore instead of a REST endpoint?

Yes, via the optional Background Geolocation Firebase adapter (sold separately). It requires a Background Geolocation license for your platform in addition to the adapter license. The adapter supports Firestore only — not the Firebase Realtime Database.


Does the demo server at tracker.transistorsoft.com store my data permanently?

No. It is a development and testing server only. Data is deleted automatically after a short period. Do not use it for production. The demo server is open-source — deploy your own instance for production use.


Account & Access

How do I access the Customer Dashboard?

Visit shop.transistorsoft.com/customers and authenticate with your GitHub account. From there you can generate license keys, manage team members, view orders, and upgrade your plan.


Why does the Customer Dashboard require a GitHub login? Our code isn't on GitHub.

GitHub is used purely as an authentication mechanism — the same concept as "Sign in with Google" or "Sign in with Apple" — so Transistor Software doesn't have to manage passwords. It has nothing to do with where your source code is hosted. You can store your app on GitLab, Bitbucket, a private server, or nowhere at all; GitHub login is only used to verify your identity when accessing the Dashboard.


The Customer Dashboard shows "Unauthorized".

This is almost always caused by an invalid or deleted GitHub account linked to your order. Contact support with your order number so the linked GitHub username can be corrected.


How do I change my GitHub account or transfer the license to a new owner?

Log in to the Customer Dashboard to manage linked GitHub usernames. For administrative transfers (changing the Dashboard admin), contact support with your order number.


Can I change the Android applicationId on an existing key?

Keys are bound to the applicationId they were generated for and cannot be changed. If you need a key for a different applicationId, contact support with your order number. There is a one-time allowance for a regeneration; additional changes may incur a fee.


Where is my invoice?

Invoices are emailed automatically after purchase. Check your spam folder. If you still cannot find it, contact support with your order number and the correct billing email address.


I get a LICENSE VALIDATION FAILURE error even though I purchased a license.

Common causes:

  1. Wrong applicationId — the applicationId in android/app/build.gradle does not match what you entered when generating the key in the Customer Dashboard.
  2. Incorrect integration — the license key <meta-data> block is in the wrong location in build.gradle, or a required setup step was skipped.
  3. Debug build without key — in debug builds, a LICENSE VALIDATION FAILURE warning is expected and the plugin remains fully functional. It is not an error in debug mode.

Enable verbose logging (logger: { debug: true, logLevel: BackgroundGeolocation.LogLevel.Verbose }), run the app, retrieve logs with BackgroundGeolocation.logger.emailLog(), and post a GitHub issue with the log file and your android/app/build.gradle.


Can I delete a key I generated by mistake?

Contact support with your order number. Keys can be destroyed and regenerated once without charge. Be precise when entering the applicationId on regeneration — subsequent changes may incur a fee. Once a key has been shipped in a production build, destroying the old key does not revoke it from devices already running that build.