This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
This is documentation for the next SDK version.
Expo Location (next)
A library that provides access to the device's location.
The
nextversion of the Location API is included in theexpo-locationlibrary. It can be used alongside the previous API. It is in beta: the API might change between SDK versions.
expo-location/next reads the device's location once, watches it while the app is open, and receives it in the background. On Android, it also lets you pick the location provider.
Installation
If you are installing this in an existing React Native app, make sure to install expo in your project.
Usage
Request the precise location
By default, a permission request asks for the precise location. On Android, this requests both ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION; when the user has already granted the approximate location, the dialog asks to change it to the precise one. On iOS, when the user has turned Precise Location off for the app, the request also shows the system prompt for temporary full accuracy.
import { Location, LocationAccuracy } from 'expo-location/next'; async function requestPreciseLocation() { const { granted, accuracy } = await Location.requestForegroundPermissions(); if (!granted) { return; } if (accuracy === LocationAccuracy.REDUCED) { // The user declined precise location. Positions are accurate to kilometers, not meters. } const position = await Location.getPosition(); if (!position) { return; } const { coordinates, horizontalAccuracy } = position; console.log(coordinates, horizontalAccuracy); }
The iOS prompt shows a default message, which the locationFullAccuracyPermission config plugin option can replace.
Request an approximate location
An approximate location is accurate to kilometers rather than meters. To get it, request the permission with accuracy: RequestPermissionsAccuracyOption.REDUCED.
On Android, this requests only ACCESS_COARSE_LOCATION, so the permission dialog does not offer the precise location.
On iOS, the app does not ask to upgrade to the precise location. To make the approximate location the default in the permission dialog, set isIosReducedAccuracyByDefault to true in the config plugin. If the app never asks for the precise location, also set locationFullAccuracyPermission to false to omit the purpose string of the temporary full accuracy prompt:
Then request reduced accuracy:
import { Location, RequestPermissionsAccuracyOption } from 'expo-location/next'; async function requestApproximateLocation() { const { granted } = await Location.requestForegroundPermissions({ accuracy: RequestPermissionsAccuracyOption.REDUCED, }); if (!granted) { return; } const position = await Location.getPosition(); if (!position) { return; } const { coordinates, horizontalAccuracy } = position; console.log(coordinates, horizontalAccuracy); }
Get the position once
getPosition resolves with a single fix. It returns a cached fix when one is younger than maxCachedAge. Otherwise, it waits up to timeout seconds for a new one and falls back to the last known location when none arrives. Both values are in seconds. Check timestamp when freshness matters.
When timeout is set to 0, the call resolves immediately with the last known position, without asking for a new fix.
import { Location, LocationProfile } from 'expo-location/next'; async function getFreshPosition() { const position = await Location.getPosition({ maxCachedAge: 30, timeout: 15, profile: LocationProfile.DEFAULT, }); if (!position) { console.log('No location available'); return null; } const ageSeconds = (Date.now() - position.timestamp) / 1000; if (ageSeconds > 30) { console.log(`Fell back to a fix from ${Math.round(ageSeconds)} seconds ago`); } return position; }
Handle a denied permission
When the user denies the permission, canAskAgain tells whether the system shows the dialog again. When it is false, the only way to grant the permission is the system settings, so send the user there.
import { Location } from 'expo-location/next'; import { Linking } from 'react-native'; async function ensureForegroundPermission() { const permission = await Location.requestForegroundPermissions(); if (permission.granted) { return true; } if (!permission.canAskAgain) { await Linking.openSettings(); } return false; }
Request the background permission
On Android, the background permission requires the foreground permission first. The request throws when the foreground permission is not granted. Receiving positions in the background also needs the background permission declared on Android and the background mode on iOS. Enable both in the config plugin:
On Android 11 and later, the system opens the app's settings page instead of a dialog. The request resolves when the user comes back.
isAndroidBackgroundLocationEnabled adds five permissions: ACCESS_BACKGROUND_LOCATION, RECEIVE_BOOT_COMPLETED, FOREGROUND_SERVICE, FOREGROUND_SERVICE_LOCATION, and POST_NOTIFICATIONS. The last three are for the location foreground service; set isAndroidForegroundServiceEnabled to false to leave them out.
On iOS, the request shows the Change to Always Allow dialog. Answering Keep Only While Using resolves with status: "denied" and scope: "whenInUse", because the foreground permission stays granted.
import { Location, LocationScope } from 'expo-location/next'; async function requestBackgroundPermission() { const { granted } = await Location.requestForegroundPermissions(); if (!granted) { return false; } const { scope } = await Location.requestBackgroundPermissions(); return scope === LocationScope.ALWAYS; }
Show live position on a screen
watchPosition returns a handle that controls the native watcher. Pause it when the screen loses focus, resume it when the screen is back, and dispose it when the screen unmounts. A screen that loses focus stays mounted behind another tab, a pushed screen, or a modal. On Android, the watcher also pauses on its own while the app is in the background, unless a location foreground service is running.
import { Location, LocationProfile, PositionWatchHandle } from 'expo-location/next'; import { useFocusEffect } from 'expo-router'; import { useCallback, useEffect, useRef } from 'react'; export default function LiveMapScreen() { const watcher = useRef<PositionWatchHandle | null>(null); useEffect(() => { watcher.current = Location.watchPosition({ profile: LocationProfile.OTHER_NAVIGATION, onPosition: position => console.log(position.coordinates), onError: error => console.warn(error.message), }); return () => { watcher.current?.dispose(); watcher.current = null; }; }, []); useFocusEffect( useCallback(() => { watcher.current?.resume(); return () => watcher.current?.pause(); }, []) ); return null; }
Handle location services turned off
Location services are a device-wide switch, separate from the app's permission. With services off, no app receives a position. On Android, enableLocationServices asks the user to turn them on: Google Play services shows an in-app prompt, and the Android provider opens the system settings. Both require the app to be in the foreground with a current Activity. On iOS, only the user can turn them on in Settings.
import { Location } from 'expo-location/next'; import { Alert, Platform } from 'react-native'; async function ensureLocationServices() { if (Location.hasLocationServicesEnabled()) { return true; } if (Platform.OS === 'android') { return Location.enableLocationServices(); } Alert.alert( 'Location Services are off', 'Turn them on in Settings > Privacy & Security > Location Services.' ); return false; }
Receive positions in the background
To receive positions while the app is in the background, define a task with defineLocationTask at the top level of a module. Then start the delivery with LocationUpdatesHandle. This requires expo-task-manager, the background permission, ACCESS_BACKGROUND_LOCATION and RECEIVE_BOOT_COMPLETED declared on Android, and the location background mode on iOS. Enable the last two in the config plugin:
Positions keep arriving after the app process has ended; the system starts the app in the background to deliver them. While the app is in the background, Android delivers positions only a few times each hour.
Import that module from the root layout, so the task exists on every launch, including the relaunch after the system terminated the app:
Then control the delivery from a screen:
On Android, a profile means something different for a background task than for a watcher. LOW_POWER waits much longer between positions, batches them, and skips positions close to the last one, so a device that stays put may produce none. AUTOMOTIVE_NAVIGATION delivers as often as the system allows, with no batching and no distance filter.
Both sides default to the same task name, so you can omit it. To run several tasks, give each task its own name and pass that name to both defineLocationTask and the LocationUpdatesHandle constructor.
Start a background session
On Android 8 and later, while the app is backgrounded, the watchers stop and location tasks trigger rarely. If you want a steady stream of position fixes in the background, start a background session with ensureBackgroundSessionStarted.
The session runs as a location foreground service with a notification. You can start it only while the app is in the foreground. It needs the FOREGROUND_SERVICE and FOREGROUND_SERVICE_LOCATION permissions, so set isAndroidForegroundServiceEnabled to true in the config plugin.
The call to ensureBackgroundSessionStarted persists the configuration passed to it. With the configuration present, the module restarts the background session whenever the app enters the foreground, even after the app is closed and reopened.
import { Location } from 'expo-location/next'; import { Platform } from 'react-native'; async function startTracking() { if (Platform.OS !== 'android') { return; } const { granted } = await Location.requestForegroundPermissions(); if (!granted) { return; } await Location.requestNotificationPermissions(); await Location.ensureBackgroundSessionStarted({ notificationTitle: 'Tracking your route', notificationBody: 'Tap to return to the app', }); } async function stopTracking() { if (Platform.OS !== 'android') { return; } await Location.stopBackgroundSession(); }
If you no longer need the background session, you need to manually stop it using stopBackgroundSession.
API
import { Location } from 'expo-location/next';
Hooks
Checks or requests the background location permission from a component. See Request the background permission.
[LocationPermissionResponse | null, RequestPermissionMethod<LocationPermissionResponse>, GetPermissionMethod<LocationPermissionResponse>]Example
const [foreground, requestForeground] = useForegroundLocationPermissions(); const [background, requestBackground] = useBackgroundLocationPermissions(); if (!foreground?.granted) { return <Button title="Allow location" onPress={requestForeground} />; } if (background?.scope !== LocationScope.ALWAYS) { return <Button title="Allow background location" onPress={requestBackground} />; }
Checks or requests the foreground location permission from a component. See Request the precise location.
[LocationPermissionResponse | null, RequestPermissionMethod<LocationPermissionResponse>, GetPermissionMethod<LocationPermissionResponse>]Example
const [permission, requestPermission] = useForegroundLocationPermissions(); if (!permission?.granted) { return <Button title="Allow location" onPress={requestPermission} />; }
Tracks whether location services are on, re-reading the switch every time the app returns to the
foreground. enable calls enableLocationServices, so it works on Android only.
See Handle location services turned off.
[enabled: boolean, enable: undefined]Example
const [enabled, enable] = useLocationServices(); if (!enabled) { return <Button title="Turn on location services" onPress={enable} />; }
Checks or requests the permission for posting notifications.
[PermissionResponse | null, RequestPermissionMethod<PermissionResponse>, GetPermissionMethod<PermissionResponse>]Classes
A location provider is the backend that serves location requests on Android. Each static method
returns a provider to pass to setLocationProvider. The default tries Google Play services first
and falls back to the Android framework, so most apps never need this.
LocationProvider Methods
The Android framework LocationManager, available without Google Play services.
SharedRef<'LocationProvider'>Tries the providers in order and uses the first one that can serve the request.
SharedRef<'LocationProvider'>The fused location provider from Google Play services. When Google Play services are absent, location requests that use this provider throw.
SharedRef<'LocationProvider'>Starts and stops the delivery of positions to the task defined with defineLocationTask.
The delivery is registered with the system, so it outlives the JS context: it survives the app
moving to the background and, with the background permission, the app being terminated.
On Android, positions keep arriving after the app process ended. Requires the background permission. While the app is in the background and no location foreground service is running, Android throttles the delivery. The handle keeps the provider selected during construction; to use a different provider, construct a new handle.
On iOS, positions come from CLLocationUpdate.liveUpdates. Requires the location background
mode and the foreground permission.
LocationUpdatesHandle Methods
Checks whether the delivery is registered with the system. It stays registered across app
restarts, so check it on launch before calling start() again.
Promise<boolean>Registers the delivery with the system and starts sending positions to the task.
On Android the promise resolves even when the provider refuses the request for updates.
Promise<void>Reads the state of the task. The state lives in the app process, so it resets whenever the process restarts, even while the task stays registered with the system.
BackgroundTaskStatusUnregisters the delivery. Positions stop arriving and the app is no longer relaunched for them.
Promise<void>Controls a running position watcher. Use watchPosition() to create one.
On Android 8 and later, the watcher stops emitting fixes when the app is backgrounded and resumes when it enters the foreground. A background session keeps it running in the background. See Start a background session.
PositionWatchHandle Methods
Subscribes to the position and error events. One watcher can have multiple listeners.
EventSubscriptionA subscription object with a remove method that stops listening for changes.
Stops delivering positions and releases the system request, keeping the watcher for resume().
voidSubscribes again with the settings staged by withProfile() and withInterval().
It does not resume a paused watcher.
booleanfalse when the provider rejects the request right away. Later failures arrive through
onError. true means that no failure occurred, not that the watcher is subscribed. On
Android, it also returns true when the watcher makes no request, for example while the app
is in the background.
Requests positions again after pause().
booleanfalse when the provider rejects the request right away. Later failures, including
every failure on iOS and with Google Play services, arrive through onError.
true means that no failure occurred, not that the watcher is subscribed. On Android, it
also returns true when the watcher makes no request, for example while the app is in the
background.
Reads the current flags of the watcher.
PositionWatchStatusStages the time between two delivered positions, in seconds. It has no effect until
restart(). Positions arrive less often when the system has no new fix. 0 delivers every
position the system produces.
On Android, it replaces the update interval the profile requests from the provider. On iOS, the system decides the rate and positions that arrive sooner than the interval are dropped.
thisThe same handle, for chaining.
Methods
Defines the task that receives the positions started with LocationUpdatesHandle.
Wraps TaskManager.defineTask, so call it in the global scope of a module imported at startup.
See Receive positions in the background.
voidNamespaces
Location Methods
Asks the user to turn location services on. With the GMS provider, this is an in-app dialog; with
the Android provider, it opens the system Settings and resolves when the user returns. Resolves
with true when services are already on.
See Handle location services turned off.
Promise<boolean>A promise that resolves to true when services are on after the user answered.
Starts the foreground service that keeps location updates flowing while the app is in the background. If the service is already promoted, re-posts its notification with the given options.
Starting the service requires the app to be in the foreground. When no service is running, a
call from the background rejects, but it still persists the options, so the service starts
the next time the app comes to the foreground.
The service keeps the options and restarts when the app is closed and reopened. Omitting
options uses the persisted configuration, or the default one if unset.
Does nothing on Android 7 and earlier, where background location is never throttled.
A missing POST_NOTIFICATIONS permission is not an error. The service still runs, but its
notification does not appear in the notification drawer.
See Start a background session.
Promise<void>Checks the background location permission without asking the user.
Promise<LocationPermissionResponse>A promise that resolves to the current permission. scope is always when it is
granted. granted is true only when the foreground permission is granted as well, while
status reflects the background permission alone.
Reads the current state of the background session.
See Start a background session.
BackgroundSessionStatusChecks the foreground location permission without asking the user.
Promise<LocationPermissionResponse>A promise that resolves to the current permission, including its scope and accuracy.
Checks the user's permission for posting notifications. See Start a background session.
Promise<PermissionResponse>A promise that fulfills with an object of type PermissionResponse.
Resolves with a single fix. It returns a cached fix when one is younger than maxCachedAge.
Otherwise, it waits up to timeout seconds for a new one and falls back to the last known
location when none arrives. With timeout 0, it returns the last known location right away.
Check timestamp when freshness matters.
On Android, when location services are off, the call still resolves with the last known
position or null if there is none.
A promise that resolves to the position, or null when the device has none.
Example
const position = await Location.getPosition({ maxCachedAge: 60, timeout: 10 }); if (!position) { console.log('No location available'); return; } const ageSeconds = (Date.now() - position.timestamp) / 1000; if (ageSeconds > 60) { console.log( `Last known location from ${Math.round(ageSeconds)} s ago, no fresh fix within the timeout` ); } console.log(position.coordinates.latitude, position.coordinates.longitude);
Gets the name of the provider currently serving positions: GMS, Android, or the chain of a
fallback, such as the default Fallback: GMS -> Android.
stringChecks whether location services are turned on for the whole device. This is the system toggle, not the app's permission: with services off, no app receives a position even when its permission is granted. See Handle location services turned off.
booleanAsks the user for permission to read the location while the app is in the background.
On Android, it throws when ACCESS_BACKGROUND_LOCATION is missing from the manifest or the
foreground permission is not granted. On iOS, answering Keep Only While Using resolves with
status denied and scope whenInUse.
See Request the background permission.
Promise<LocationPermissionResponse>A promise that resolves to the permission after the user answered.
Asks the user for permission to read the location while the app is in the foreground.
Shows the system dialog only while the permission is undetermined; afterwards it resolves with
the current state, so check canAskAgain to know whether the user has to go to the system
settings.
See Request the precise location and Handle a denied permission.
Promise<LocationPermissionResponse>A promise that resolves to the permission after the user answered.
Asks the user to grant the permission for posting notifications. On Android 12 and lower, no dialog appears, and the result reports whether notifications are on for the app.
See Start a background session.
Promise<PermissionResponse>A promise that fulfills with an object of type PermissionResponse.
Selects the provider that serves all later location requests.
voidStops the foreground service and clears the persisted configuration.
See Start a background session.
Promise<void>Starts watching the position and returns the handle that controls the watcher.
See Show live position on a screen.
PositionWatchHandleThe handle for controlling the watcher.
Example
const watcher = Location.watchPosition({ profile: LocationProfile.FITNESS, onPosition: (position) => console.log(position.coordinates), onError: (error) => console.warn(error.code, error.message), }); // When the positions are no longer needed: watcher.dispose();
Types
State of the background session, returned by getBackgroundSessionStatus.
A set of coordinates expressed in decimal degrees.
Example
{ latitude: 40.7128, longitude: -74.0060 }
Returned by the permission functions, with the location-specific scope and accuracy.
Type: PermissionResponse extended by:
A single location fix.
Example
{ coordinates: { latitude: 50.0614, longitude: 19.9372 }, horizontalAccuracy: 12.5, // meters timestamp: 1791277756380, // milliseconds since the Unix epoch altitude: 253.1, // meters above the WGS 84 ellipsoid mslAltitude: 212.4, // meters above mean sea level verticalAccuracy: 4.8, // meters mocked: false, heading: 87.3, // degrees clockwise from true north, course over ground headingAccuracy: 5, // degrees speed: 1.4, // meters per second speedAccuracy: 0.3, // meters per second }
One positionChanged event from the native watcher: a position or an error, never both.
Type: object shaped as below:
Or object shaped as below:
Options for requestForegroundPermissions and
requestBackgroundPermissions.
Enums
Lifecycle of the Android foreground service.
BackgroundSessionState.NOT_RUNNING = "NOT_RUNNING"No service is running, and none is being started or stopped.
BackgroundSessionState.PENDING = "PENDING"The service is in transition. It is either starting and not yet promoted to the foreground, or stopping and not yet destroyed.
LocationAccuracy.FULL = "full"The app receives the precise location.
On Android, ACCESS_FINE_LOCATION is granted.
On iOS, Precise Location is turned on for the app.
A use case that each platform turns into an accuracy, an update interval, and a power budget.
On Android it maps to a LocationRequest priority, interval, batching, and a minimum distance
between updates. The mapping differs between the position watcher and background location
updates, which use longer intervals for most profiles.
On iOS 17 and later it maps to a CLLocationUpdate.LiveConfiguration; on earlier versions to a
CLLocationManager activity type with the best available accuracy.
LocationProfile.AIRBORNE = "airborne"Flying, such as in a plane or a glider.
On Android, high accuracy with the most frequent updates.
On iOS, CLLocationUpdate.LiveConfiguration.airborne.
LocationProfile.AUTOMOTIVE_NAVIGATION = "automotiveNavigation"Turn-by-turn navigation in a vehicle.
On Android, high accuracy with the most frequent updates.
On iOS, CLLocationUpdate.LiveConfiguration.automotiveNavigation.
LocationProfile.DEFAULT = "default"General-purpose positioning, for anything the other profiles do not cover.
On Android, balanced priority.
On iOS, CLLocationUpdate.LiveConfiguration.default.
LocationProfile.FITNESS = "fitness"Workout tracking such as running or cycling.
On Android, high accuracy with frequent updates.
On iOS, CLLocationUpdate.LiveConfiguration.fitness.
LocationProfile.LOW_POWER = "lowPower"Coarse position at the lowest power use.
On Android, low-power priority with the least frequent updates, which the provider can delay
and deliver in batches. The Android provider requests it from NETWORK_PROVIDER, or from
PASSIVE_PROVIDER when NETWORK_PROVIDER is not available.
On iOS, kilometer accuracy with a 3 km distance filter, on every iOS version.
LocationScope.ALWAYS = "always"On Android, ACCESS_BACKGROUND_LOCATION is granted.
On iOS, the authorization is Always.
The accuracy a permission request asks for.
RequestPermissionsAccuracyOption.FULL = "full"Asks for the precise location.
On Android, requests ACCESS_FINE_LOCATION together with ACCESS_COARSE_LOCATION.
On iOS, when the app holds a reduced accuracy, shows the system prompt for temporary full
accuracy after the permission is granted. The prompt needs the purpose string the config plugin
writes by default; locationFullAccuracyPermission: false removes it and skips the prompt.
Permissions
Android
Installing expo-location adds ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION.
The config plugin adds the rest when isAndroidBackgroundLocationEnabled is true. Enabling ACCESS_BACKGROUND_LOCATION or the location foreground service in a new build requires submitting the app for review. See Google Play's requirements for background location permissions and foreground services.
iOS
This library uses the following Info.plist keys. The config plugin writes a default value for each of them except NSLocationDefaultAccuracyReduced.