This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
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 ACCESS_FINE_LOCATION. 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.
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. 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.
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.
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. 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 declared on Android, and the location background mode on iOS. Enable the last two in the config plugin:
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:
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.
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} />; }
Classes
The backends that can serve positions on Android. The default tries Google Play services first
and falls back to the Android framework, so most apps never need this. Pass the result to
setLocationProvider.
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.
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.
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.
Promise<void>Unregisters 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.
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.
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.
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.
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.
Checks the foreground location permission without asking the user.
Promise<LocationPermissionResponse>A promise that resolves to the current permission, including its scope and accuracy.
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.
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 requires the foreground permission first. 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.
Selects the provider that serves all later location requests.
voidStarts 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
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
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 and interval.
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 updates about every second.
On iOS, CLLocationUpdate.LiveConfiguration.airborne.
LocationProfile.AUTOMOTIVE_NAVIGATION = "automotiveNavigation"Turn-by-turn navigation in a vehicle.
On Android, high accuracy with updates about every second.
On iOS, CLLocationUpdate.LiveConfiguration.automotiveNavigation.
LocationProfile.DEFAULT = "default"General-purpose positioning, for anything the other profiles do not cover.
On Android, balanced priority with updates about every 5 seconds.
On iOS, CLLocationUpdate.LiveConfiguration.default.
LocationProfile.FITNESS = "fitness"Workout tracking such as running or cycling.
On Android, high accuracy with updates about every 2 seconds.
On iOS, CLLocationUpdate.LiveConfiguration.fitness.
LocationProfile.LOW_POWER = "lowPower"Coarse position at the lowest power use.
On Android, low-power priority with updates about every 60 seconds, which the provider can delay by up to 5 minutes and deliver in batches.
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 the following permissions:
ACCESS_COARSE_LOCATION: for the approximate device locationACCESS_FINE_LOCATION: for the precise device location
The following permissions are optional and enabled through the config plugin:
ACCESS_BACKGROUND_LOCATION: to read the location while the app is in the background or closed. Enabling it in a new build requires submitting the app for review.
iOS
This library uses the following Info.plist keys. The config plugin writes a default value for each of them except NSLocationDefaultAccuracyReduced.