Config
Config is a Kotlin data class whose every property defaults to null, and
toJson() omits the nulls — so a Config built from one property is a valid
patch, not a request to clear everything else. Unknown keys coming back from a
newer engine are stored rather than rejected. It is applied in two ways:
ready(config)— called once, typically at app launch. It mergesconfigover the persisted config from the previous session (or the SDK defaults documented on this page, on first run) and, if tracking was left enabled, auto-resumes it.setConfig(config)— merges a partial or full config into the live tracker at any time afterwards.
Most keys take effect immediately on setConfig(). Two documented exceptions
on this page — locationFilterPolicy and
kalmanProfile — are only applied when the location filter
is rebuilt, which happens at tracking start/stop, not live on setConfig().
If you need a filter-tuning change to take effect immediately, call
setConfig() then stop()/start().
This page covers every documented Config key: Permissions, Geolocation,
Motion & activity recognition, HTTP, Persistence, Authorization, Application,
Logging & debug, and Geofencing.
All keys at a glance
| Key | Default | One-liner |
|---|---|---|
locationAuthorizationRequest | 'Always' | Authorization level to request: 'Always' or 'WhenInUse'. |
locationAuthorizationAlert | none (disabled unless set) | Strings for the native “insufficient permission” dialog. |
disableLocationAuthorizationAlert | false | Suppresses the locationAuthorizationAlert dialog. |
backgroundPermissionRationale | none | No-op — accepted for API compatibility only. |
desiredAccuracy | effectively HIGH when unset | Accuracy tier requested while moving. |
distanceFilter | 10 | Minimum metres between fixes while moving. |
disableElasticity | false | Pins distanceFilter to its base value regardless of speed. |
elasticityMultiplier | 1.0 | Scales how fast distanceFilter grows with speed. |
stationaryRadius | 200 | Radius (metres) of the stationary geofence/wake region. |
stationaryKeepAlive | true | Keeps a low-power location stream alive while stationary. |
stationaryDesiredAccuracy | 'BALANCED' | Accuracy tier for the stationary keep-alive stream. |
stationaryDistanceFilter | 75 | iOS: displacement gate for the stationary stream. |
stationaryLocationUpdateInterval | 30000 | Android: interval for the stationary stream. |
locationUpdateInterval | 1000 | Android: fused-location request interval while moving. |
useSessionEngine | true | iOS 17+: selects the modern session-based delivery path. |
showsBackgroundLocationIndicator | false | iOS: shows the OS background-location pill (Always; session-path hiding in beta). |
disableLocationFilter | false | Bypasses the Kalman/accuracy/teleport filter entirely. |
locationFilterMaxAccuracy | 100 | Accuracy gate (metres) — worse fixes rejected outright. |
locationFilterMaxSpeed | 60 | Teleport-rejection speed threshold, metres/second. |
locationFilterPolicy | 'Conservative' | Teleport-decision policy: Conservative / Adjust / PassThrough. |
kalmanProfile | 'DEFAULT' | Kalman smoothing tuning. |
odometerAccuracyThreshold | 0 (off) | Accuracy gate specifically for the odometer. |
stopTimeout | 5 | Minutes of stillness before committing to stationary. |
motionTriggerDelay | 0 | Debounce before trusting a moving activity classification. |
triggerActivities | "in_vehicle,on_bicycle,on_foot,running,walking" | Activity types that count as moving. |
minimumActivityRecognitionConfidence | 75 (Android) · 50 (iOS) | Minimum activity-recognition confidence to trust. |
activityRecognitionInterval | 10000 | Android: poll interval for Activity Recognition. |
disableMotionActivityUpdates | false | Ignores activity-recognition updates entirely. |
url | none (persist-only mode) | Upload endpoint. |
method | 'POST' | HTTP verb for uploads. |
headers | {} | Extra headers on every upload/log-flush request. |
params | {} | Merged into the request body root. |
extras | {} | Merged into every uploaded record’s own extras. |
httpRootProperty | 'location' | Body key that carries the location payload. |
autoSync | true | Auto-uploads whenever a new record is queued. |
autoSyncThreshold | 0 | Queue depth that triggers an auto-sync. |
disableAutoSyncOnCellular | false | Defers auto-sync while the active network is cellular. |
batchSync | false | Uploads multiple queued records in a single request. |
maxBatchSize | -1 (unlimited) | Cap on records per batchSync request. |
httpTimeoutMs | 30000 | Connect/read/write timeout for uploads and log flushes. |
maxDaysToPersist | 2 (engine fallback) | Age-based pruning of queued records, in days. |
maxRecordsToPersist | -1 (unlimited) | Count cap on the offline queue, evicting oldest rows. |
authorization | none | Native, killed-app-safe JWT bearer-token refresh config. |
stopOnTerminate | false | Whether tracking stops when the app process is terminated. |
startOnBoot | false | Whether tracking resumes automatically after a reboot. |
heartbeatInterval | 60 | Seconds between heartbeat events. |
preventSuspend | false | iOS: holds a background task to extend backgrounded life. |
foregroundService | n/a | No-op — accepted for API compatibility only. |
notification | see table | Android: configures the foreground-service notification. |
debug | false | Plays an audible cue per tracking event, both platforms. |
logLevel | 0 (OFF) | Gates what’s persisted to the native log store. |
logMaxDays | 3 (min 1) | Retention for persisted log rows, in days. |
logUrl | none (local-only) | Endpoint for batched native log upload. |
diagnosticExtras | false | iOS, test devices only: adds a diagnostic snapshot to uploads. |
geofenceProximityRadius | 1000 | Radius (metres) used for proximity-slicing OS-registered geofences. |
maxMonitoredGeofences | -1 (platform budget as-is) | Cap on how many geofences are registered with the OS. |
geofenceInitialTriggerEntry | true | Requests a synthetic ENTER for already-inside geofences. |
Permissions
See the permissions guide for the store-manifest declarations these keys assume are already in place.
locationAuthorizationRequest
Type: string · Default: 'Always'
The authorization level the SDK asks for: 'Always' or 'WhenInUse'
(case-insensitive; any other value is treated as 'WhenInUse'). Leaving this
unset behaves exactly like 'Always' — both engines default to wanting Always
when the key is absent.
This key drives two things:
requestPermission()’s staged Android chain only requestsACCESS_BACKGROUND_LOCATIONwhen this is'Always'; with'WhenInUse'the chain stops after foreground location + activity recognition. On iOS, this key decides whether an already-granted When In Use authorization is treated as sufficient ('WhenInUse', resolves immediately) or insufficient ('Always', a further Always prompt or Settings nudge is needed).- It gates whether an
AuthorizedWhenInUsestatus counts as “not enabled enough” forlocationAuthorizationAlert’s Settings-nudge dialog.
Set this to 'WhenInUse' only if your use case genuinely doesn’t need
background tracking — with the SDK default ('Always'), a device stuck at
When-In-Use will keep re-prompting/nudging.
BackgroundGeolocation.ready( Config( locationAuthorizationRequest = "Always", // ... ),)locationAuthorizationAlert
Type: object · Default: none (alert disabled unless set)
A dictionary of strings used to build the native “your location permission
isn’t sufficient” dialog, shown automatically after a
requestPermission()
call (or an app-foreground authorization re-check) leaves authorization
insufficient per locationAuthorizationRequest,
or leaves device Location Services switched off entirely. Recognized keys
(all optional, each falls back to a hardcoded English default if omitted):
| Key | Shown when |
|---|---|
titleWhenOff | Location Services are off at the OS level. |
titleWhenNotEnabled | Location Services are on but app authorization is insufficient. |
instructions | Body text under the title (empty by default). |
cancelButton | Dismiss button label (default "Cancel"). |
settingsButton | Button that deep-links to the app’s Settings page (default "Settings"). |
If this key is left unset entirely, no dialog is ever shown — this is opt-in,
not a default nag screen. Suppress it after presenting your own onboarding UI
with disableLocationAuthorizationAlert.
BackgroundGeolocation.ready( Config( locationAuthorizationRequest = "Always", locationAuthorizationAlert = mapOf( "titleWhenNotEnabled" to "Background location is off", "instructions" to "Open Settings and choose \"Allow all the time\".", ), ),)disableLocationAuthorizationAlert
Type: boolean · Default: false
Suppresses the locationAuthorizationAlert
dialog entirely, even if that key is configured. Use this once your app has
its own onboarding flow that already explains and requests background
location, so the SDK doesn’t show a second, redundant prompt.
backgroundPermissionRationale
Type: object · Default: none
Accepted for API compatibility but currently does nothing on either platform:
no-op on iOS, and on Android the OS’s own permission-rationale flow
(shouldShowRequestPermissionRationale) is used instead of a config-driven
dialog. See Limitations.
Geolocation
These keys govern GPS behavior across the moving/stationary state machine — see Tracking lifecycle for how they fit together.
desiredAccuracy
Type: number · Default: effectively HIGH accuracy when unset
One of the DESIRED_ACCURACY_* constants
(a lower/negative number requests better accuracy, larger numbers coarser and
cheaper fixes). Governs the accuracy tier requested from the OS location
provider while moving; the stationary state has its own tier — see
stationaryDesiredAccuracy. Leaving it unset is
treated as the highest-accuracy tier on both platforms.
BackgroundGeolocation.ready( Config(desiredAccuracy = DesiredAccuracy.HIGH.value),)distanceFilter
Type: number · Units: metres · Default: 10
The minimum displacement between fixes while moving. This is the primary
lever for point density vs. battery/data usage — smaller values produce a
denser, smoother route at the cost of more GPS wake-ups and more uploaded
records; larger values sample less often. Unless
disableElasticity is set, the effective filter
distance is scaled by current speed — see
elasticityMultiplier — so distanceFilter is best
understood as the base value at low speed, not a fixed sampling interval.
A battery-saving profile that trades point density for battery, useful for long-running background tracking where a precise route isn’t critical:
BackgroundGeolocation.ready( Config( distanceFilter = 50.0, // coarser base spacing elasticityMultiplier = 1.5, // scale it up further at speed stationaryRadius = 300.0, // wider stop detection, fewer false departures ),)disableElasticity
Type: boolean · Default: false
Pins distanceFilter to its configured base value
regardless of speed, disabling the speed-elastic scaling described under
elasticityMultiplier. Useful when you want
predictable, constant-distance sampling for a controlled comparison or a
fixed-cadence use case, at the cost of oversampling at highway speed (or
undersampling at walking speed) relative to the elastic default.
elasticityMultiplier
Type: number · Default: 1.0
Scales how aggressively distanceFilter grows with speed
(ignored if disableElasticity is true). Values above
1.0 widen the gap between fixes faster as speed increases (fewer points on
a highway drive, more battery/data savings); values below 1.0 flatten the
curve, keeping spacing closer to the flat distanceFilter value even at
speed. See the distanceFilter battery-saving example
above for a combined profile.
stationaryRadius
Type: number · Units: metres · Default: 200
The radius of the stationary geofence: once the motion state machine decides the device is parked, tracking arms this radius around the last stopped location as both (a) the wake trigger — a raw fix that clears the radius immediately re-engages tracking, faster than waiting on activity recognition — and (b) the OS-level wake-region on iOS, which is what allows the app to be relaunched by the OS after a kill or force-quit once the device leaves the radius. A larger radius trades a slower departure-wake for fewer false re-arms in a large parking lot or driveway; a smaller radius wakes sooner but is more prone to spurious wakes from GPS jitter while genuinely parked. See Boot & killed-app behavior for how this interacts with relaunch.
stationaryKeepAlive
Type: boolean · Default: true
Keeps a low-power, coarse location request alive while stationary instead of
letting GPS fully sleep. This trades a small amount of stationary battery
drain for a much faster wake at trip start (the stream already has a rough
fix to detect displacement from, rather than needing to cold-start GPS).
Setting this false restores a fully-sleeping-GPS stationary state — lower
battery draw while parked, but slower to notice departure.
stationaryDesiredAccuracy
Type: string · Default: 'BALANCED'
Accuracy tier for the stationary keep-alive stream (see
stationaryKeepAlive): 'HIGH' | 'BALANCED' |
'LOW'. Deliberately never drops to a cell-tower-grade 'LOW'-equivalent
fix by default — a coarse fix (hundreds to thousands of metres of error)
would be rejected wholesale by the locationFilterMaxAccuracy
gate, leaving the stationary wake path with no usable input to detect
departure at all. Raise to 'HIGH' only if you’ve confirmed the battery cost
is acceptable for your use case — it is not, by itself, a guarantee of a
faster departure wake.
stationaryDistanceFilter
Type: number · Units: metres · Default: 75
The displacement gate applied to the low-power stationary keep-alive stream
on iOS (see stationaryKeepAlive) — independent of,
and typically smaller than, stationaryRadius. No-op on
Android, where the stationary stream’s cadence is instead governed by
stationaryLocationUpdateInterval.
stationaryLocationUpdateInterval
Type: number · Units: milliseconds · Default: 30000
The fused-location (Android’s Fused Location Provider, part of Play Services)
request interval for the stationary keep-alive stream on Android — the
time-based counterpart to iOS’s distance-based
stationaryDistanceFilter. No-op on iOS.
locationUpdateInterval
Type: number · Units: milliseconds · Default: 1000
The fused-location request interval while moving on Android. Because
Android’s fused provider delivers at most one fix per this interval
regardless of distanceFilter, a coarse interval silently
caps point density even with a tight distanceFilter — at highway speed a
5000 ms interval alone would space points roughly 5 seconds apart no matter
how small distanceFilter is set. No-op on iOS, which has no equivalent
request-interval knob (Core Location delivers based on displacement).
useSessionEngine
Type: boolean · Default: true
Selects the modern iOS location delivery path — CLLocationUpdate.liveUpdates
plus CLBackgroundActivitySession — instead of the legacy
CLLocationManager.startUpdatingLocation request, which iOS aggressively
suspends between significant-location-change wakes in the background. The
trade-off: the session path’s CLBackgroundActivitySession keeps the blue
background-location indicator visible whenever tracking is enabled; with
Always authorization plus
showsBackgroundLocationIndicator: false the engine now skips that session to hide the pill (beta — see that
key). This
key exists purely as a remote-config kill-switch: it defaults to true
and should only be set to false to force the legacy path (e.g. while
diagnosing a regression suspected to be session-engine-specific). Devices on
iOS below 17 always use the legacy path regardless of this flag. Android
silently ignores this key (stored but unread) — it’s iOS-only.
// iOS-only key. Setting it on Android is accepted and ignored, so a config// shared with an iOS build stays valid.BackgroundGeolocation.setConfig(Config(useSessionEngine = false))showsBackgroundLocationIndicator
Type: boolean · Default: false
Controls the blue “app is using your location in the background” pill while
tracking with Always authorization. On the legacy delivery path
(useSessionEngine: false, and always on iOS below
17) it maps straight to CLLocationManager.showsBackgroundLocationIndicator
(Apple QA1965 semantics). On the default session path
CLBackgroundActivitySession shows the indicator unconditionally — the pill
is that API’s keep-alive mechanism — so there false + Always makes the
engine skip creating the activity session entirely, resting background
delivery on Always plus the location background mode. Beta: that
session-path suppression still needs field verification that
CLLocationUpdate.liveUpdates keeps delivering in the background and across
eviction relaunches without the activity session (Apple doesn’t document
it); until then useSessionEngine: false remains the battle-tested way to
hide the pill. Two OS rules no setting can override: with When-In-Use
authorization the pill is always shown, and the small status-bar location
arrow is never suppressible. No-op on Android, which has no equivalent
system indicator (the foreground-service notification is the closest
analogue there — see the Application section).
disableLocationFilter
Type: boolean · Default: false
Bypasses the Kalman smoothing + accuracy gate + teleport-rejection filter entirely — every raw fix the OS delivers is accepted as-is. Useful for debugging (comparing raw vs. filtered output) but not recommended for production: without the accuracy gate, cell-tower-grade fixes with hundreds of metres of error are accepted into the route and the odometer.
locationFilterMaxAccuracy
Type: number · Units: metres · Default: 100
The accuracy gate: fixes reporting worse accuracy than this are rejected by
the filter outright, in every locationFilterPolicy
(this gate applies even under 'PassThrough', which otherwise skips teleport
rejection and Kalman smoothing). Lowering it rejects more borderline fixes
(cleaner route, but more silence in weak-signal conditions like indoors or
dense urban canyons); raising it admits noisier fixes. No-op if
disableLocationFilter is true.
locationFilterMaxSpeed
Type: number · Units: metres/second · Default: 60
The teleport-rejection threshold: the maximum speed implied between two
consecutive fixes before the newer one is treated as a GPS jump rather than
real movement. 60 m/s is ~216 km/h — generous enough for normal driving,
tight enough to catch the multi-hundred-metre single-fix jumps that dense
urban GPS reflection sometimes produces. How a violation is handled depends
on locationFilterPolicy: dropped ('Conservative'),
capped to this speed instead of dropped ('Adjust'), or ignored entirely
('PassThrough').
locationFilterPolicy
Type: string · Default: 'Conservative'
Selects the filter’s teleport-decision phase (case-insensitive):
'Conservative'(default) — a fix that implies a speed abovelocationFilterMaxSpeedis dropped outright. Safest choice for a route that must never show impossible jumps, at the cost of occasionally discarding a real fast-but-legitimate fix (e.g. a GPS fix taken right after a long tunnel).'Adjust'— a teleporting fix is capped to the kinematic limit instead of dropped, so the point still lands in the route (nudged back toward the previous fix) rather than vanishing. Prefer this when you’d rather see an approximate point than a gap in the route.'PassThrough'— only thelocationFilterMaxAccuracygate applies; there is no teleport rejection and no Kalman smoothing at all. Use this for debugging the raw filtered-vs-unfiltered difference, or if your own backend already does its own smoothing/outlier rejection.
Applied when the filter is rebuilt at tracking start/stop — changing it
via setConfig() on a live tracker has no effect until the next stop()/
start() cycle.
// Prefer an approximate point over a gap in the route (delivery/fleet// tracking, where a visible route matters more than strict fidelity).BackgroundGeolocation.setConfig(Config(locationFilterPolicy = "Adjust"))BackgroundGeolocation.stop()BackgroundGeolocation.start() // rebuilds the filter with the new policykalmanProfile
Type: string · Default: 'DEFAULT'
Selects the Kalman filter’s smoothing tuning (case-insensitive):
'DEFAULT' | 'AGGRESSIVE' (responds faster to real direction/speed
changes, at the cost of a noisier-looking path) | 'CONSERVATIVE' (maximum
smoothing, at the cost of more lag following a sudden turn or stop). Like
locationFilterPolicy, this is applied when the
filter is rebuilt at tracking start/stop — not live on setConfig().
odometerAccuracyThreshold
Type: number · Units: metres · Default: 0 (off)
A separate, odometer-only accuracy gate: fixes with accuracy worse than this
threshold are still accepted into the route and uploaded normally, but they
do not advance the running odometer total. 0 (the default) disables
this gate — every accepted fix advances the odometer regardless of its
accuracy. Raise this if you’ve observed odometer drift from noisy-but-still-
filter-passing fixes (e.g. urban multipath) and want a tighter bar
specifically for distance accounting, without also tightening
locationFilterMaxAccuracy and losing those
fixes from the route entirely.
Motion & activity recognition
These keys tune the automatic motion-state machine described in Tracking lifecycle.
stopTimeout
Type: number · Units: minutes · Default: 5
How long the device must be continuously classified as still before the
motion state machine commits to the stationary state (arms
stationaryRadius, lets GPS drop to the stationary
power profile). A shorter timeout saves battery sooner after a real stop but
risks false stops during traffic lights or short stops on a route; a longer
timeout is more tolerant of brief stops but keeps GPS at moving-power cost
longer. The countdown does not restart if already armed, and is cancelled by
either a confirmed non-still activity or a raw displacement large enough to
indicate the device never actually stopped.
motionTriggerDelay
Type: number · Units: milliseconds · Default: 0
A debounce delay before a moving-type activity classification is trusted
and acted on (transitioning out of stationary). 0 (the default) acts
immediately on the first qualifying activity update. Raise this if you’re
seeing spurious brief “moving” blips (e.g. being jostled while parked)
trigger a false departure; the tradeoff is a slower real departure wake.
triggerActivities
Type: string · Default: "in_vehicle,on_bicycle,on_foot,running,walking"
A comma-separated list of activity-recognition type names that count as
“moving” for the motion state machine (recognized names: in_vehicle,
on_bicycle, on_foot, running, walking; still and unknown are
never treated as moving). Unrecognized names in the CSV are silently
dropped. Narrow this list if a particular activity type is producing false
departures for your use case — e.g. dropping on_foot if brief walking
around a parked vehicle shouldn’t count as a trip start.
BackgroundGeolocation.ready( Config( triggerActivities = "in_vehicle,on_bicycle,running", minimumActivityRecognitionConfidence = 75, ),)minimumActivityRecognitionConfidence
Type: number · Default: 75 (Android) · 50 (iOS)
Minimum confidence (0-100) an activity-recognition update must carry before
it’s trusted by the motion state machine. The two platforms’ scales aren’t
directly comparable, which is why the defaults differ: Android’s Activity
Recognition API reports a genuine 0-100 confidence, so 75 is a real
three-quarters bar. iOS’s Core Motion only reports a coarse
low/medium/high tier, which this SDK maps onto the same 0-100 scale as
33/66/100 — so iOS’s default of 50 is deliberately pitched to sit
between the low and medium tiers, i.e. “at least medium confidence”,
not a literal half-confidence bar. Raising this makes the classifier more
conservative (fewer false moving/stationary transitions, but slower to
react to a real one); lowering it reacts faster at the cost of more noise.
activityRecognitionInterval
Type: number · Units: milliseconds · Default: 10000
How often the Android Activity Recognition client is polled for an update. No-op on iOS, where Core Motion activity updates are push-delivered rather than polled on an interval.
disableMotionActivityUpdates
Type: boolean · Default: false
Ignores activity-recognition updates entirely. With this true, the motion
state machine falls back to its activity-independent signals only: raw
speed/displacement to detect moving, and the
stationaryRadius geofence exit to detect departure
from a stop. Use this if activity recognition is unreliable or unavailable
on a target device class and you’d rather rely purely on GPS-derived motion,
accepting a less precise (and typically slower) stop/start detection.
HTTP
BGeo uploads locations natively — a durable SQLite-backed queue drains to
your server without JS in the hot path, and survives app kill/reboot. See the
HTTP upload & authorization guide for the full
picture, and sync() /
getLocations() /
getCount() for manual queue
inspection.
url
Type: string · Default: none (persist-only mode)
The endpoint each queued record is POSTed (or method’d) to. Leaving this
unset doesn’t disable tracking — it puts the SDK in persist-only mode:
locations still queue in SQLite and still reach JS via onLocation, but
nothing is uploaded until you set a url (or call
sync() after setting one) yourself.
method
Type: string · Default: 'POST'
The HTTP verb for uploads: 'POST' | 'PUT' | 'PATCH' (case-insensitive).
Any other value — including an empty string or a typo — silently falls back
to 'POST' rather than rejecting the config.
headers
Type: object · Default: {}
Extra headers sent with every upload and log-flush request. If
authorization.accessToken is set, it overwrites any
Authorization entry you put here; a later token refresh overwrites it again.
params
Type: object · Default: {}
Merged into the request body root alongside the location payload — useful for constant fields your backend expects on every call (a device ID, an API version). See Shaping the body.
extras
Type: object · Default: {}
Merged into every uploaded record’s own extras object (a per-call extras —
e.g. one passed to insertLocation() —
wins over this on key conflicts). Distinct from params, which
lands at the body root rather than inside each record.
httpRootProperty
Type: string · Default: 'location'
The body key that carries the location payload:
{ "location": { coords: …, timestamp: …, … } } by default, or
{ "location": [ {…}, {…} ] } once batchSync is uploading
more than one record. Set this to "." to merge a single record’s fields
directly into the body root instead of nesting them — this only applies when
exactly one record is being sent (unset or batchSync: false); a multi-record
batch always nests under a real key regardless of this setting.
// Body becomes { coords: {...}, timestamp: "...", is_moving: true, ... }// instead of { location: { ... } } — for a backend with a flat per-request// schema.BackgroundGeolocation.ready( Config( url = "https://your-server.example/locations", httpRootProperty = ".", ),)autoSync
Type: boolean · Default: true
Automatically attempts an upload whenever a new record is queued (subject to
autoSyncThreshold and
disableAutoSyncOnCellular). Set this false
to queue everything and drain it only on your own explicit
sync() calls.
autoSyncThreshold
Type: number · Default: 0
The queue depth that triggers an auto-sync. 0 (the default) fires on
every newly-queued record — the least batchy setting. Raising it defers
auto-sync until at least this many records are pending, which pairs naturally
with batchSync to accumulate a full batch before uploading.
disableAutoSyncOnCellular
Type: boolean · Default: false
Defers auto-sync while the device’s active network is cellular, letting the
queue build up until Wi-Fi/ethernet is available (checked on every
connectivity change — a device that’s already on cellular when this becomes
true also pauses immediately). An explicit
sync() call always uploads
regardless of network type — this key only gates the automatic path.
batchSync
Type: boolean · Default: false
Uploads multiple queued records in a single request
({ "location": [ {…}, {…} ] }, capped at maxBatchSize)
instead of one request per record. Combine with
autoSyncThreshold to control how large a batch
accumulates before it fires.
// Wait for at least 25 queued records, then upload up to 100 per request.BackgroundGeolocation.ready( Config( url = "https://your-server.example/locations", autoSync = true, autoSyncThreshold = 25, batchSync = true, maxBatchSize = 100, ),)maxBatchSize
Type: number · Default: -1 (unlimited — sends every pending record in one batch)
Caps how many records a single batchSync request carries.
No-op while batchSync is false (every upload is already a single record).
httpTimeoutMs
Type: number · Units: milliseconds · Default: 30000
Applied uniformly as the connect, read, and write timeout for the upload
client. Covers both location uploads and logUrl log-flush
requests.
Persistence
See Data pipeline for how queued records flow through the SQLite-backed offline queue.
maxDaysToPersist
Type: number · Units: days · Default: 2 (engine fallback; the key itself is unset by default)
Prunes queued records older than this many days — unconditionally, on every
configure(). The engine hardcodes a 2-day retention window; setting this
key only takes effect when the value is positive, in which case it
replaces the 2-day fallback (raising or lowering it). Leaving it unset, or
setting 0 or a negative number, leaves the engine at its 2-day default —
there is no way to disable age-based pruning with this key. In practice
this means records older than 2 days are silently pruned from the offline
queue unless you explicitly raise maxDaysToPersist. Distinct from
logMaxDays, which governs native log retention, not location
records.
maxRecordsToPersist
Type: number · Default: -1 (unlimited)
Caps the offline queue by record count, evicting the oldest rows once the
cap is exceeded. A device tracking continuously with no connectivity (and no
maxDaysToPersist) will otherwise grow the queue without bound; a real
deployment typically sets both this and maxDaysToPersist to keep the SQLite
database bounded. See Data pipeline — persistence
for the storage details.
Authorization
authorization
Type: object · Default: none
Configures native, killed-app-safe bearer-token refresh for the upload queue. When the uploader gets a 401/403 on a request, it exchanges the current refresh token for a fresh access token on the native side, with no JS context required — so a queue draining while your app is killed or suspended doesn’t stall on an expired token until the user happens to reopen the app.
| Sub-key | Type | Description |
|---|---|---|
strategy | 'JWT' | The only supported strategy today. |
accessToken | string | Sent as Authorization: Bearer <token>, overwriting any Authorization you set in headers. |
refreshToken | string | The current refresh token. Native refresh only overwrites this with a genuinely new, non-empty value — a config push that omits it (or resends a stale one) can’t clobber a token the native side already rotated while the app was killed. |
refreshUrl | string | POST endpoint the refresh request is sent to. |
refreshPayload | object | Refresh request body template. Any string value containing the literal {refreshToken} has it substituted with the current refresh token. Default (when unset): { "refresh_token": "<refreshToken>" }. |
refreshHeaders | object | Extra headers on the refresh request (a JSON Content-Type is added automatically and can’t be overridden here). |
The refresh response is parsed tolerantly — { access_token, refresh_token }
or { accessToken, refreshToken }, optionally nested under a data key — so
it works against a variety of auth backends without a custom adapter.
Refresh outcomes (success or failure, with the HTTP status) surface via the
onAuthorization event — see the Events reference
and the HTTP guide’s authorization section.
Because a background refresh can rotate the tokens while your app isn’t
running, prefer getAuthState()
over your app’s own persisted copy when resuming in the foreground.
BackgroundGeolocation.ready( Config( url = "https://your-server.example/locations", authorization = AuthorizationConfig( strategy = "JWT", accessToken = "eyJhbGciOi...", refreshToken = "a1b2c3...", refreshUrl = "https://your-server.example/auth/refresh", refreshPayload = JSONObject().put("refresh_token", "{refreshToken}"), refreshHeaders = mapOf("X-Api-Key" to "..."), ), ),)
BackgroundGeolocation.onAuthorization { event -> if (event.optBoolean("success")) { // Adopt the rotated pair in your own persisted copy — or read it back // later with getAuthState(). Do not log this object: it carries live // credentials. }}Application
stopOnTerminate
Type: boolean · Default: false
Whether tracking should stop when the app process is terminated. Honoured on
both platforms, but by different mechanisms: Android tears tracking down
directly in the foreground service’s onTaskRemoved. iOS has no reliable
terminate hook at all, so it’s honoured at next relaunch instead — init
drops the persisted enabled flag before any auto-resume path runs, so a
relaunch (background or foreground) sees tracking disabled and does not
resume it. See the Boot & killed-app behavior guide.
startOnBoot
Type: boolean · Default: false
Whether tracking should resume automatically after a device reboot, if it was
enabled when the device went down. Android’s implementation matches its name
literally: a BootReceiver restarts the service on
BOOT_COMPLETED/LOCKED_BOOT_COMPLETED when both isEnabled() and this flag
are true; with false, nothing survives a reboot (geofences, the
activity-recognition PendingIntent, the foreground service — all die with
the process) until the app is next opened by the user.
iOS has no reboot broadcast to hook, so true here is best-effort by
design: there’s no dedicated boot handler, but the wake-region and
significant-location-change registrations persist in locationd across a
reboot and relaunch the app in the background once — device-verified as
roughly a minute after boot, with no movement required. false is still
honoured: on relaunch, init detects the reboot (via kern.boottime,
tolerant of clock drift, not systemUptime which pauses during device sleep)
and drops the persisted enabled flag before any auto-resume path runs, so a
later relaunch finds tracking disabled and does nothing.
heartbeatInterval
Type: number · Units: seconds · Default: 60
How often a heartbeat event fires while tracking is enabled — this timer
runs unconditionally alongside tracking, not gated behind a separate opt-in.
Each firing carries the last known location and, if the device has appeared
stationary-yet-still-moving for a whole interval, nudges the stop-timeout
countdown. See the Events reference for
the heartbeat payload.
preventSuspend
Type: boolean · Default: false
Holds a beginBackgroundTask, renewed on every heartbeat, to keep the app
process alive a little longer while backgrounded and stationary. No-op on
Android, which relies on the (always-on) foreground service instead. Treat
this as a minor extension, not a background-execution guarantee — iOS
background tasks are time-limited regardless, and this key cannot turn them
into a permanent entitlement.
foregroundService
Type: boolean · Default: n/a — has no effect either way
Accepted for API compatibility but does nothing on either platform: the Android foreground service required for background location runs whenever tracking is enabled, regardless of this key’s value; no-op on iOS, which has no equivalent concept. See Limitations.
notification
Type: object · Default: see table
Configures the Android foreground-service notification shown while tracking
is enabled. No-op on iOS, which has no foreground-service notification
concept — showsBackgroundLocationIndicator
is the closest iOS analogue.
| Sub-key | Type | Default | Description |
|---|---|---|---|
title | string | 'Location' | Notification title. |
text | string | 'Location tracking active' | Notification body text. |
channelId | string | 'bgeo_location_min' | Android notification channel ID. |
channelName | string | 'Location' | User-visible channel name (Settings → App notifications). |
smallIcon | string | app launcher icon | "drawable/name" or "mipmap/name" — an invalid or missing resource falls back to the app’s own icon. |
color | string | none | "#RRGGBB". An unparseable value is logged and ignored (notification keeps the system default tint). |
priority | number | -2 (MIN) | Transistor-style NOTIFICATION_PRIORITY_* scale: -2 MIN .. 2 MAX. |
priority also sets the notification channel’s importance at creation
time — and Android freezes a channel’s importance once created; re-posting
the notification with a different priority on the same channelId
does not change it retroactively. To actually raise or lower importance after
the app has run once, change channelId to a new value alongside priority
so Android creates a fresh channel.
BackgroundGeolocation.ready( Config( notification = NotificationConfig( title = "Tracking active", text = "Your location is being recorded", channelId = "bgeo_location_default", priority = 0, // NotificationCompat.PRIORITY_DEFAULT ), ),)Logging & debug
debug
Type: boolean · Default: false
Plays a one-shot audible cue per event (location, heartbeat, motion-change, stop-timeout start/cancel; geofence events reuse the location cue) on both platforms. Every cue is playable in Debug sound cues, where you can learn which sound means what before taking a device into the field. This is a development convenience only — it does not affect tracking, filtering, or upload behavior in any way, and should never be used to explain a change in GPS behavior.
logLevel
Type: number · Default: 0 (OFF)
Gates how much is persisted into the native log store (0 OFF .. 5
VERBOSE). Every log line is still mirrored unconditionally to
logcat/os_log regardless of this setting — logLevel only controls what’s
additionally kept in the on-device log table for
getLog() and optional upload via
logUrl. See the Logging & debugging guide.
logMaxDays
Type: number · Units: days · Default: 3 (coerced up to a minimum of 1)
How long persisted log rows are retained locally before being pruned, on top of a hard 25,000-row cap that also bounds the table regardless of age.
logUrl
Type: string · Default: none (log rows stay local-only)
Absolute URL for batched native log upload ({ "events": [{ ts, level, src, event, message?, data? }, …] }), using the same headers/authorization/token-refresh
machinery as location uploads. Rows are flushed on several triggers (after a
location-queue drain, on heartbeat, on app foreground, once 100 rows are
pending, or on an explicit uploadLog()
call) plus a short foreground-only coalescing delay so a line logged by an
idle foregrounded app doesn’t wait for the next heartbeat. A 2xx or 4xx
response marks a batch delivered; a 429 (the server throttles log ingestion
per device) or 5xx/network failure leaves it pending for the next flush.
Uploaded rows aren’t deleted immediately — they remain available to
getLog() until logMaxDays/the row cap prunes them.
diagnosticExtras
Type: boolean · Default: false
Adds a compact native diagnostic snapshot (fix counters, app/motion state,
active manager configuration) into every uploaded record’s extras. This is
a debugging aid for reproducing field issues on instrumented test
devices — leave it off for your production fleet, since it adds payload
weight and noise to every single location record for no operational benefit.
Android currently ignores this key (accepted, no-op) — there is no Android
implementation yet.
Geofencing
See addGeofence() /
addGeofences() and the
Geofencing guide for registering
app-facing geofences; these three keys tune how they’re mapped onto the
platform’s own (limited) geofence budget.
geofenceProximityRadius
Type: number · Units: metres · Default: 1000
Of all the geofences you’ve registered with addGeofence(),
only those within this radius of the device’s last known fix are actually
registered with the OS at any given time (proximity slicing) — see
maxMonitoredGeofences for the count cap applied on
top of this radius filter. As the device moves, the set of OS-registered
geofences is re-evaluated and swapped accordingly.
maxMonitoredGeofences
Type: number · Default: -1 (use the platform’s own budget as-is: 19 iOS / 99 Android)
A further cap on how many geofences (of those already inside
geofenceProximityRadius) are registered with the
OS at once. Any value <= 0 leaves the platform’s own hard budget as the
only limit; set a smaller positive number to stay further under budget
deliberately (e.g. to leave headroom for a separately-managed region).
geofenceInitialTriggerEntry
Type: boolean · Default: true
Requests a synthetic ENTER event for any geofence that’s already-inside at
the moment it’s registered with the OS (iOS requestStateForRegion / Android
INITIAL_TRIGGER_ENTER), rather than waiting for a genuine boundary crossing
that may never come if the device is already inside. Set false if you only
want events for geofences actually crossed after registration.