Skip to content

Permissions & background location

Background location is the #1 source of store-review rejections, and the single most common reason a tracking integration “stops working” once a tester backgrounds the app. Both platforms treat background location as a separate, more sensitive grant than ordinary foreground location — you (the app) have to declare it, justify it to the store, and request it at runtime. The SDK cannot do any of that for you.

Foreground-only authorization is not a degraded version of background tracking — it’s a different mode entirely:

  • iOS: with only When In Use authorization, Core Location delivers updates while your app is visible (and briefly after backgrounding), then stops. There is no continuous route once the app is truly backgrounded — the OS will not launch UIBackgroundModes: location delivery without Always authorization.
  • Android: without ACCESS_BACKGROUND_LOCATION, the OS stops delivering location fixes to your app the moment it isn’t in the foreground — regardless of whether a foreground service and its notification are still running. Fine/coarse location alone only covers foreground use.

If your product needs a route while the phone is in the user’s pocket, plan for Always/background from the start — not as a follow-up permission you can add later without re-doing onboarding.

iOS

When In Use vs. Always

iOS authorization is staged:

  1. When In Use — the app can read location while foregrounded. This is the initial authorization iOS grants from the first location prompt.
  2. Always — required for background delivery. Depending on how your Info.plist keys and prompt are set up, iOS either offers Always directly on the first prompt, or upgrades from When In Use to Always with a second, separate system prompt shown later (sometimes after the app has used location in the foreground for a while). Exactly when that second prompt appears is decided by iOS, not the app — don’t build logic that assumes it fires at a specific moment.

The SDK’s requestPermission() requests location authorization only; on iOS it does not request Motion authorization itself — see Rolling your own permission flow below.

Precise Location

Since iOS 14, users can grant Precise or Reduced location accuracy independently of When In Use/Always. Reduced accuracy is a valid, permanent user choice — the SDK and your app must work acceptably with it if you support it. The Accuracy authorization constants (ACCURACY_AUTHORIZATION_FULL / ACCURACY_AUTHORIZATION_REDUCED) reflect the current state; read them from getProviderState() or the accuracyAuthorization field on onProviderChange.

The iOS location-authorization prompt in the example app, offering Allow Once, Allow While Using App, and Don't Allow.The Android location-permission dialog with the Precise / Approximate accuracy choice above While using the app, Only this time, and Don't allow.

The first location prompt is where accuracy is decided: iOS grants full or reduced accuracy alongside When In Use (left), while Android makes the Precise / Approximate choice explicit in the dialog itself (right).

If your feature genuinely requires full accuracy for a single session (e.g. a one-off “share my exact location now” action) rather than as a persistent grant, request it explicitly:

const accuracy = await BackgroundGeolocation.requestTemporaryFullAccuracy('DeliverFullAccuracy');

purpose ('DeliverFullAccuracy' above) must exactly match a key you’ve added to NSLocationTemporaryUsageDescriptionDictionary in Info.plist — see Info.plist keys for the full key list.

Info.plist recap

The keys themselves — NSLocationAlwaysAndWhenInUseUsageDescription, NSLocationWhenInUseUsageDescription, NSMotionUsageDescription, UIBackgroundModeslocation, and the optional NSLocationTemporaryUsageDescriptionDictionary — are documented with sample values in Info.plist keys. Write real justification strings for each: App Store review rejects vague or generic usage descriptions, especially for the Always/background string.

The iOS Always-authorization upgrade prompt: 'Allow BGeoExample to also use your location even when you are not using the app?' with Keep Only While Using and Change to Always Allow.

The Always upgrade arrives as its own prompt, worded from the usage description you wrote — iOS decides when to show it.

Android

Declare the permission

The SDK’s AAR merges foreground and fine/coarse location permissions into your app automatically (see the full merged-vs-declared table in Permissions merged from the engine AAR), but not ACCESS_BACKGROUND_LOCATION — Google Play requires the app, not a library, to own that declaration. Add it to your app manifest:

android/app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

FINE vs. COARSE, and background

ACCESS_FINE_LOCATION gives GPS-grade fixes; ACCESS_COARSE_LOCATION is a network/cell-grade fallback the OS may downgrade to. Neither implies background access on its own: starting with Android 10, an app needs the separate ACCESS_BACKGROUND_LOCATION grant — commonly surfaced to the user as “Allow all the time” — to keep receiving fixes once it’s no longer in the foreground. On newer Android versions the OS increasingly declines to offer “Allow all the time” inline alongside the foreground prompt, and instead routes the user to the app’s system Settings page to pick it as a second, separate step.

The Android per-app Location permission settings screen with 'Allow all the time' selected and the 'Use precise location' toggle on.

Activity recognition and the degraded fallback

ACTIVITY_RECOGNITION drives the moving/still motion state machine via Play Services. If the user denies it (or it’s never granted), tracking still runs, but in a degraded mode: motion detection falls back to speed-based heuristics plus a roughly 200-meter stationary geofence to re-engage motion detection when the device moves off. Request it — and explain why in your own onboarding UI — rather than treating it as optional.

The Android physical-activity permission dialog: 'Allow BGeoExample to access your physical activity?' with Allow and Don't allow.

Foreground-service notification

Android 13+ requires POST_NOTIFICATIONS for the tracking foreground service’s persistent notification; it’s part of the AAR’s merged permission set (see the full table in Permissions merged from the engine AAR). Without it, the notification can’t be shown, but the OS still requires the foreground service to run for background tracking to work reliably.

Request at runtime

await BackgroundGeolocation.requestPermission();

requestPermission() runs a staged Android chain rather than one combined dialog — and it’s not an unconditional linear sequence. It starts at (and only requests) the first permission that’s still missing, skipping any step already granted, whether that’s on the very first call or a later one:

  1. If background location and activity recognition are already both granted, it returns immediately with the current status — no dialogs, no re-prompting for permissions the user already granted.
  2. Otherwise, if fine/coarse location isn’t granted yet, it requests that first. If fine/coarse is already granted, this step is skipped entirely — the call goes straight to step 3.
  3. Once fine/coarse is settled, if locationAuthorizationRequest is 'Always' (the default) and background isn’t already granted, it requests ACCESS_BACKGROUND_LOCATION (“Allow all the time”). With 'WhenInUse', or if background is already granted, this step is skipped.
  4. Once background is settled, if activity recognition isn’t already granted, it requests ACTIVITY_RECOGNITION. If it’s already granted, this step is skipped too.
  5. It resolves with one of the AUTHORIZATION_STATUS_* constants.

So a call from an app that already holds fine/coarse location jumps straight to the background (or activity-recognition) step — it never re-requests fine/coarse. The early-return in step 1 checks both background and activity recognition — not just background — specifically so the activity-recognition prompt is never silently skipped on a return visit.

flowchart TD
    A[requestPermission called] --> B{Background AND Activity\nRecognition already granted?}
    B -->|Yes| Z[Early return: current status]
    B -->|No| C{FINE/COARSE\nalready granted?}
    C -->|No| C1[Request FINE / COARSE location]
    C1 --> D
    C -->|Yes, skip| D{locationAuthorizationRequest == 'Always'\nAND background not granted?}
    D -->|Yes| D1["Request BACKGROUND\n(ACCESS_BACKGROUND_LOCATION,\n'Allow all the time')"]
    D1 --> E
    D -->|No, skip| E{Activity Recognition\nalready granted?}
    E -->|No| E1[Request ACTIVITY_RECOGNITION]
    E1 --> F[Resolve AUTHORIZATION_STATUS_*]
    E -->|Yes, skip| F

On iOS, requestPermission() requests location authorization only (When In Use → Always, per locationAuthorizationRequest); there is no equivalent staged chain to draw, since Activity/Motion authorization is a separate system prompt outside this call.

Google Play prominent disclosure

Play policy requires a prominent in-app disclosure before the permission prompt, explaining what you collect and why, plus a Play Console Location permissions declaration form. Apps that track in the background without an approved declaration are rejected or removed. Budget review time for this.

Rolling your own permission flow

requestPermission() is convenient but opinionated about ordering. Nothing requires you to use it — many apps drive permissions step-by-step with a library like react-native-permissions instead, so the request order can be interleaved with custom onboarding UI (e.g. showing a rationale screen between the When In Use and Always steps, or requesting iOS Motion authorization — PERMISSIONS.IOS.MOTION — at a specific point in onboarding). Both approaches are fully supported: the SDK never requests permissions on its own initiative outside an explicit requestPermission() call, so ready() and start() will simply reflect whatever authorization state already exists when you call them.

Settings nudges

Once a user has denied or downgraded a permission, the only way back is the system Settings app — the OS won’t re-show its own prompt. The SDK can optionally nudge the user there for you:

  • locationAuthorizationRequest — declares whether the app wants 'Always' or 'WhenInUse'; this also decides what counts as “insufficient” for the nudge below.
  • locationAuthorizationAlert — an opt-in dialog (no default English strings are ever shown unless you set this) offering a button that deep-links to the app’s Settings page, shown automatically when authorization is insufficient per locationAuthorizationRequest, or when Location Services are off entirely.
  • disableLocationAuthorizationAlert — suppresses that dialog once your own onboarding already covers it, so the user doesn’t see two prompts.

Reacting to permission changes

Authorization can change at any time outside your app — the user can revoke it from Settings while your app is backgrounded. Subscribe to onProviderChange to react to that, or call getProviderState() for a one-shot read:

const sub = BackgroundGeolocation.onProviderChange(({ status, enabled, gps }) => {
const hasBackgroundAuth = status === BackgroundGeolocation.AUTHORIZATION_STATUS_ALWAYS; // 3
// ...update your UI / prompt the user back to Settings
});

Compare status against AUTHORIZATION_STATUS_ALWAYS (3) specifically — not just “truthy” or “not denied” — since AUTHORIZATION_STATUS_WHEN_IN_USE (4) is also a valid, non-denied status but does not authorize background tracking. See the full Authorization status table for every value.