npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

expo-background-tracking

v1.0.5

Published

Background geolocation & geofencing for Expo — battery-conscious motion-detection, SQLite persistence, HTTP sync. Expo Go compatible (foreground), full background in dev builds. No Firebase.

Readme

expo-background-tracking

GPS tracking & geofencing for Expo (Android + iOS), 100% Expo, no Firebase, no custom native code, Expo Go compatible.

Battery-efficient motion detection (accelerometer + GPS speed), SQLite persistence, HTTP sync with offline queue, unlimited geofencing (circles + polygons), odometer, weekly scheduling.

Installation

npm install expo-background-tracking
npx expo install expo-location expo-task-manager expo-sqlite expo-sensors expo-battery expo-network expo-device expo-constants

Expo Go Compatibility

| | Expo Go | Dev build / production | |---|---|---| | Tracking app in foreground | ✅ | ✅ | | Tracking app in background | ❌* | ✅ | | Geofencing | ✅ (software, foreground) | ✅ (native + software) | | SQLite, HTTP sync, odometer, scheduler, motion-detection | ✅ | ✅ |

* Official Expo limitation: background location requires a development build (Expo docs). The library automatically detects the environment — same code everywhere, zero crashes in Expo Go.

For full background (dev build), add to app.json:

{
  "expo": {
    "ios": { "infoPlist": { "UIBackgroundModes": ["location", "fetch"] } },
    "android": { "permissions": ["ACCESS_BACKGROUND_LOCATION", "FOREGROUND_SERVICE", "FOREGROUND_SERVICE_LOCATION"] },
    "plugins": [["expo-location", {
      "isAndroidBackgroundLocationEnabled": true,
      "isAndroidForegroundServiceEnabled": true
    }]]
  }
}

Usage

import BackgroundGeolocation from 'expo-background-tracking';

// 1. Listen to events
const sub = BackgroundGeolocation.onLocation((location) => {
  console.log('[location]', location.coords);
});

BackgroundGeolocation.onMotionChange(({ isMoving, location }) => {
  console.log('[motionchange]', isMoving);
});

BackgroundGeolocation.onGeofence(({ identifier, action }) => {
  console.log('[geofence]', identifier, action);
});

// 2. Configure (ready, once only)
const state = await BackgroundGeolocation.ready({
  desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
  distanceFilter: 10,
  stopTimeout: 5,
  url: 'https://your-server.com/locations', // HTTP sync — native fetch, no Firebase
  autoSync: true,
  batchSync: false,
  headers: { 'X-API-KEY': 'xyz' },
  locationAuthorizationRequest: 'Always', // 'Always' | 'WhenInUse'
  debug: true,
});

// 3. Start — permissions are requested automatically by the library.
// start() calls requestForegroundPermissions + requestBackgroundPermissions
// (when locationAuthorizationRequest === 'Always' and running in a dev build).
// If you need to request permissions manually before starting:
//
//   const status = await BackgroundGeolocation.requestPermission();
//   // status: AuthorizationStatus.ALWAYS | WHEN_IN_USE | DENIED
//   if (status === BackgroundGeolocation.AUTHORIZATION_STATUS_DENIED) {
//     console.warn('Location permission denied');
//     return;
//   }
//
if (!state.enabled) {
  await BackgroundGeolocation.start();
}

Geofencing (unlimited, circles + polygons)

await BackgroundGeolocation.addGeofence({
  identifier: 'HOME',
  latitude: 48.8566,
  longitude: 2.3522,
  radius: 200,
  notifyOnEntry: true,
  notifyOnExit: true,
  notifyOnDwell: true,
  loiteringDelay: 60000,
});

// Polygon (software-evaluated)
await BackgroundGeolocation.addGeofence({
  identifier: 'ZONE',
  vertices: [[48.85, 2.34], [48.86, 2.34], [48.86, 2.36], [48.85, 2.36]],
});

await BackgroundGeolocation.startGeofences(); // geofences-only mode

Sending HTTP locations to your server

The library includes a complete HTTP service (based on fetch, no Firebase): each location is persisted in SQLite then sent to your url. On failure (offline, server error), locations remain queued and are automatically resent on reconnection.

Basic configuration

await BackgroundGeolocation.ready({
  url: 'https://your-server.com/locations', // destination for locations
  method: 'POST',                             // 'POST' (default) | 'PUT' | 'OPTIONS'
  headers: {                                  // custom HTTP headers
    'X-API-KEY': 'abc123',
    'Authorization': 'Bearer ...',
  },
  params: {                                   // fields added at JSON root level
    device_id: 'phone-01',
    company_id: 7,
  },
  extras: {                                   // attached to EVERY location sent
    user_id: 42,
    mission: 'delivery',
  },
  autoSync: true,                             // automatically send each location
  httpTimeout: 60000,                         // request timeout in ms
});

Control transmission count and frequency

| Option | Effect | |---|---| | autoSync: true | Automatically send each new location | | autoSyncThreshold: 10 | Only send when 10 locations accumulate in queue | | batchSync: true | Send locations in batches (1 HTTP request = N locations) | | maxBatchSize: 50 | Maximum 50 locations per request (batches chain together) | | autoSync: false + sync() | 100% manual sending, when you decide |

// Example: save battery/network — 1 request per 25 points, in batches
await BackgroundGeolocation.ready({
  url: 'https://your-server.com/locations',
  autoSync: true,
  autoSyncThreshold: 25,
  batchSync: true,
  maxBatchSize: 100,
});

// Manual send (e.g., on button or at mission end)
const uploaded = await BackgroundGeolocation.sync();
console.log(`${uploaded.length} locations sent`);

Payload format received by your server

By default (httpRootProperty: 'location'):

{
  "location": {
    "uuid": "f3f57a91-…",
    "timestamp": "2026-06-12T14:32:11.000Z",
    "odometer": 1842.5,
    "is_moving": true,
    "coords": {
      "latitude": 48.856614, "longitude": 2.352222,
      "accuracy": 5, "speed": 1.4, "heading": 270, "altitude": 35
    },
    "activity": { "activity": "walking", "confidence": 75 },
    "battery": { "level": 0.82, "is_charging": false },
    "extras": { "user_id": 42, "mission": "delivery" }
  },
  "device_id": "phone-01",
  "company_id": 7
}

With batchSync: true, "location" becomes an array of locations.

Customize format

await BackgroundGeolocation.ready({
  url: '…',
  httpRootProperty: 'data',          // { "data": {...} } — or '.' for no wrapper
  locationTemplate: '{"lat":<%= latitude %>,"lng":<%= longitude %>,"ts":<%= timestamp %>,"batt":<%= battery.level %>}',
});

Template variables: latitude, longitude, accuracy, speed, heading, altitude, timestamp, uuid, odometer, is_moving, activity.type, activity.confidence, battery.level, battery.is_charging.

Track transmissions

BackgroundGeolocation.onHttp((response) => {
  console.log('[http]', response.status, response.success, response.responseText);
});

Persistence & manual sync

const count = await BackgroundGeolocation.getCount();        // queued locations
const locations = await BackgroundGeolocation.getLocations(); // read queue
const uploaded = await BackgroundGeolocation.sync();          // send now
await BackgroundGeolocation.destroyLocations();               // empty queue

Retention options: maxDaysToPersist (default 1 day), maxRecordsToPersist, persistMode (ALL | LOCATION | GEOFENCE | NONE).

Scheduling

await BackgroundGeolocation.ready({
  schedule: ['1-5 09:00-17:00', '6 10:00-14:00'], // Mon-Fri 9am-5pm, Sat 10am-2pm
});
await BackgroundGeolocation.startSchedule();

Odometer, sensors, state

const km = (await BackgroundGeolocation.getOdometer()) / 1000;
await BackgroundGeolocation.resetOdometer();
const sensors = await BackgroundGeolocation.getSensors();
const providerState = await BackgroundGeolocation.getProviderState();
const powerSave = await BackgroundGeolocation.isPowerSaveMode();

JWT auth with automatic refresh (on 401)

await BackgroundGeolocation.ready({
  url: 'https://api.example.com/locations',
  authorization: {
    strategy: 'JWT',
    accessToken: 'eyJ...',
    refreshToken: 'def...',
    refreshUrl: 'https://api.example.com/auth/refresh',
  },
});
BackgroundGeolocation.onAuthorization((event) => console.log(event));

Complete API

Events: onLocation, onMotionChange, onActivityChange, onGeofence, onGeofencesChange, onHeartbeat, onHttp, onProviderChange, onConnectivityChange, onPowerSaveChange, onEnabledChange, onSchedule, onAuthorization.

Methods: ready, start, stop, startGeofences, startSchedule, stopSchedule, changePace, getCurrentPosition, watchPosition, stopWatchPosition, getState, setConfig, reset, getLocations, getCount, insertLocation, destroyLocations, destroyLocation, sync, getOdometer, setOdometer, resetOdometer, addGeofence, addGeofences, removeGeofence, removeGeofences, getGeofences, getGeofence, geofenceExists, getSensors, getDeviceInfo, isPowerSaveMode, getProviderState, requestPermission, requestTemporaryFullAccuracy, registerHeadlessTask, startBackgroundTask, stopBackgroundTask, getLog, destroyLog, emailLog, removeListeners.

Test app

The test app demonstrates live location tracking, geofencing (ENTER/EXIT/DWELL), motion detection (MOVING/STATIONARY), heartbeat, HTTP sync, and the full event log.

cd testapp
npm install
npx expo start --clear

License

MIT