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

apple-health

v0.0.4

Published

Apple HealthKit bindings for Expo

Readme

apple-health

An Expo module for interacting with Apple HealthKit on iOS devices. Read and write health data, subscribe to real-time updates, display activity rings, and more.

Features

  • 70+ quantity types - Steps, heart rate, calories, distance, sleep, nutrition, and more
  • 40+ category types - Sleep analysis, symptoms, mindfulness, reproductive health
  • 80+ workout types - Running, cycling, swimming, yoga, and more
  • React hooks - usePermissions, useHealthKitQuery, useHealthKitStatistics, useHealthKitSubscription, useHealthKitAnchor
  • Fluent builders - HealthKitQuery and HealthKitSampleBuilder for imperative use
  • Real-time subscriptions - Get notified when health data changes
  • Background delivery - Process health updates when your app is in the background
  • Activity rings - Native Apple Watch-style activity ring visualization
  • Full TypeScript support - Complete typings for all APIs

Installation

npx expo install apple-health

Configuration

Add the config plugin to your app.json or app.config.js:

{
  "expo": {
    "plugins": [
      [
        "apple-health",
        {
          "healthSharePermission": "Allow $(PRODUCT_NAME) to read your health data",
          "healthUpdatePermission": "Allow $(PRODUCT_NAME) to write your health data",
          "backgroundDelivery": true
        }
      ]
    ]
  }
}

Plugin Options

| Option | Type | Description | | ------------------------ | --------- | -------------------------------------------------------- | | healthSharePermission | string | Custom message for read permission prompt | | healthUpdatePermission | string | Custom message for write permission prompt | | backgroundDelivery | boolean | Enable background delivery for health updates | | isClinicalDataEnabled | boolean | Enable clinical records access (requires Apple approval) |

Quick Start

import {
  usePermissions,
  useHealthKitQuery,
  useHealthKitStatistics,
  PermissionStatus,
} from "apple-health/hooks";

export default function App() {
  // Request authorization with Expo-style hook
  const [status, requestPermission] = usePermissions({
    read: ["stepCount", "heartRate", "sleepAnalysis"],
    write: ["stepCount"],
  });

  // Query samples with a hook
  const { data: heartRates } = useHealthKitQuery({
    type: "heartRate",
    kind: "quantity",
    limit: 10,
    skip: status?.status !== PermissionStatus.GRANTED,
  });

  // Get aggregated statistics
  const { data: steps } = useHealthKitStatistics({
    type: "stepCount",
    aggregations: ["cumulativeSum"],
    startDate: new Date(Date.now() - 24 * 60 * 60 * 1000),
    skip: status?.status !== PermissionStatus.GRANTED,
  });

  return (
    <View>
      <Button title="Authorize" onPress={requestPermission} />
      <Text>Status: {status?.status}</Text>
      <Text>Today's steps: {steps?.sumQuantity ?? 0}</Text>
    </View>
  );
}

Authorization

usePermissions Hook (Recommended)

The usePermissions hook follows Expo's permission pattern with automatic status fetching:

import { usePermissions, PermissionStatus } from "apple-health/hooks";

function App() {
  const [status, requestPermission, getPermission] = usePermissions({
    read: ["stepCount", "heartRate", "sleepAnalysis"],
    write: ["stepCount"],
  });

  if (status?.status === PermissionStatus.GRANTED) {
    return <Text>Access granted!</Text>;
  }

  return <Button title="Grant Access" onPress={requestPermission} />;
}

Hook options:

| Option | Type | Description | | --------- | ---------- | ---------------------------------------- | | read | string[] | Data types to request read access for | | write | string[] | Data types to request write access for | | get | boolean | Auto-fetch status on mount (default: true) | | request | boolean | Auto-request on mount (default: false) |

Return value: [status, requestPermission, getPermission]

  • status - Current permission status (HealthKitPermissionResponse | null)
  • requestPermission - Function to request permissions
  • getPermission - Function to refresh current status

Status properties:

| Property | Type | Description | | ------------- | ------------------- | ------------------------------------ | | status | PermissionStatus | 'granted', 'denied', or 'undetermined' | | granted | boolean | Whether all permissions are granted | | canAskAgain | boolean | Whether the user can be prompted | | expires | 'never' | Permissions don't expire | | permissions | object | Detailed per-type authorization status |

Imperative API

For non-React contexts, use the module directly:

import AppleHealth from "apple-health";

const result = await AppleHealth.requestAuthorization({
  read: ["stepCount", "heartRate", "sleepAnalysis"],
  write: ["stepCount"],
});

console.log(result.status); // 'notDetermined' | 'sharingDenied' | 'sharingAuthorized'

Or use the standalone async functions:

import { getPermissionsAsync, requestPermissionsAsync } from "apple-health/hooks";

// Check current status
const status = await getPermissionsAsync({
  read: ["stepCount"],
  write: ["stepCount"],
});

// Request permissions
const result = await requestPermissionsAsync({
  read: ["stepCount", "heartRate"],
  write: ["stepCount"],
});

Important: HealthKit intentionally hides read authorization status for privacy. The granted and status properties only reflect write permissions. For read-only access, the status will always be undetermined. To verify read access, try querying data directly.

React Hooks

usePermissions

Request and check HealthKit authorization using Expo's permission pattern:

import { usePermissions, PermissionStatus } from "apple-health/hooks";

const [status, requestPermission, getPermission] = usePermissions({
  read: ["stepCount", "heartRate"],
  write: ["stepCount"],
  // get: true,    // Auto-fetch on mount (default)
  // request: false, // Auto-request on mount
});

// Check status (reflects write permissions only)
if (status?.granted) {
  // Write permissions granted
}

// Request permissions
await requestPermission();

// Refresh status
await getPermission();

Note: HealthKit hides read permission status for privacy. The granted property only reflects write permissions. For read-only apps, query data directly to verify access.

useHealthKitQuery

Fetch health samples with automatic lifecycle management:

import { useHealthKitQuery } from "apple-health/hooks";

const {
  data, // HealthKitSample[] | null
  isLoading,
  error,
  refetch,
  deleteSample,
} = useHealthKitQuery({
  type: "heartRate",
  kind: "quantity", // 'quantity' | 'category' | 'workout'
  limit: 10,
  ascending: false,
  startDate: new Date("2024-01-01"),
  endDate: new Date(),
  skip: !authorized, // Skip query until authorized
});

// Samples have a delete() method
const handleDelete = async (sample) => {
  await deleteSample(sample);
};

useHealthKitStatistics

Get aggregated statistics for quantity types:

import { useHealthKitStatistics } from "apple-health/hooks";

// Single aggregated result
const { data: todaySteps } = useHealthKitStatistics({
  type: "stepCount",
  aggregations: ["cumulativeSum"],
  startDate: todayStart,
  endDate: new Date(),
});
// data.sumQuantity = 8500

// Time-bucketed results (returns array)
const { data: weeklySteps } = useHealthKitStatistics({
  type: "stepCount",
  aggregations: ["cumulativeSum"],
  interval: "day", // 'hour' | 'day' | 'week' | 'month' | 'year'
  startDate: weekAgo,
  endDate: new Date(),
});
// data = [{ startDate, endDate, sumQuantity }, ...]

Aggregation types:

  • cumulativeSum - Total sum (steps, calories, distance)
  • discreteAverage - Average value (heart rate, temperature)
  • discreteMin - Minimum value
  • discreteMax - Maximum value
  • mostRecent - Most recent value

useHealthKitSubscription

Get notified when HealthKit data changes:

import { useHealthKitSubscription } from "apple-health/hooks";

const { isActive, lastUpdate, start, pause, resume, unsubscribe } =
  useHealthKitSubscription({
    type: "stepCount",
    onUpdate: () => {
      // Data changed - refetch your queries
      refetchSteps();
    },
    autoStart: true,
  });

useHealthKitAnchor

Paginated incremental sync for large datasets:

import { useHealthKitAnchor } from "apple-health/hooks";

const {
  samples, // All fetched samples
  deletedObjects, // Tracked deletions for sync
  hasMore,
  isLoading,
  fetchMore,
  reset,
  getAnchorState, // Serialize for persistence
} = useHealthKitAnchor({
  type: "stepCount",
  kind: "quantity",
  limit: 50, // Fetch 50 at a time
  persistenceKey: "stepCount-anchor", // Auto-persist anchor state
});

// Load more samples
<Button
  title={`Load More (${samples.length} loaded)`}
  onPress={fetchMore}
  disabled={!hasMore}
/>;

Imperative API

HealthKitQuery

Fluent query builder for one-off queries:

import { HealthKitQuery } from "apple-health";

const samples = await new HealthKitQuery()
  .type("sleepAnalysis", "category")
  .dateRange(yesterday, today)
  .limit(10)
  .ascending(false)
  .execute();

// Get statistics
const stats = await new HealthKitQuery()
  .type("stepCount", "quantity")
  .dateRange(weekAgo, today)
  .aggregations(["cumulativeSum", "discreteAverage"])
  .executeStatistics();

// Time-bucketed statistics
const dailyStats = await new HealthKitQuery()
  .type("heartRate", "quantity")
  .dateRange(weekAgo, today)
  .interval("day")
  .aggregations(["discreteAverage", "discreteMin", "discreteMax"])
  .executeStatistics();

HealthKitSampleBuilder

Create and save health samples:

import { HealthKitSampleBuilder } from "apple-health";

// Save a quantity sample
const stepsSample = await new HealthKitSampleBuilder()
  .quantityType("stepCount")
  .value(1000)
  .unit("count")
  .startDate(hourAgo)
  .endDate(new Date())
  .save();

// Save a category sample
const sleepSample = await new HealthKitSampleBuilder()
  .categoryType("sleepAnalysis")
  .categoryValue(4) // 4 = deep sleep
  .startDate(lastNight)
  .endDate(thisMonring)
  .save();

// Save a workout
const workoutSample = await new HealthKitSampleBuilder()
  .workoutType("running")
  .startDate(thirtyMinutesAgo)
  .endDate(new Date())
  .totalEnergyBurned(250) // kcal
  .totalDistance(5000) // meters
  .metadata({ HKIndoorWorkout: false })
  .save();

Subscriptions & Anchors

HealthKitSubscription

Low-level subscription for real-time updates:

import { HealthKitSubscription } from "apple-health";

const subscription = new HealthKitSubscription("stepCount");

subscription.onUpdate = () => {
  console.log("Step count changed!");
};

subscription.start();
// ... later
subscription.unsubscribe();

HealthKitAnchor

Paginated sync with deletion tracking:

import { HealthKitAnchor } from "apple-health";

const anchor = new HealthKitAnchor("stepCount", "quantity");

// Restore previous anchor state
const savedState = await AsyncStorage.getItem("anchor-state");
if (savedState) anchor.restore(savedState);

// Fetch samples
const { samples, deletedObjects, hasMore } = await anchor.fetchNext(50);

// Process deletions for sync
for (const deleted of deletedObjects) {
  await localDB.delete(deleted.uuid);
}

// Save anchor state for next session
await AsyncStorage.setItem("anchor-state", anchor.serialize());

Background Delivery

Receive health updates when your app is in the background:

import AppleHealth from "apple-health";
import { useEvent } from "expo";

// Enable background delivery (once per type)
await AppleHealth.enableBackgroundDelivery("stepCount", "hourly");
// frequency: 'immediate' | 'hourly' | 'daily'

// Listen for background updates
const event = useEvent(AppleHealth, "onBackgroundDelivery");
useEffect(() => {
  if (event) {
    console.log("Background update:", event.typeIdentifier);
  }
}, [event]);

// Disable when no longer needed
await AppleHealth.disableBackgroundDelivery("stepCount");
await AppleHealth.disableAllBackgroundDelivery();

Activity Rings

Display Apple Watch-style activity rings:

import { ActivityRingView } from "apple-health";
import AppleHealth from "apple-health";

// Fetch today's activity summary
const summaries = await AppleHealth.queryActivitySummary(
  today.toISOString(),
  today.toISOString()
);

<ActivityRingView
  summary={{
    activeEnergyBurned: 420,
    activeEnergyBurnedGoal: 500,
    appleExerciseTime: 25,
    appleExerciseTimeGoal: 30,
    appleStandHours: 10,
    appleStandHoursGoal: 12,
  }}
  style={{ width: 150, height: 150 }}
/>;

User Characteristics

Read user profile data (requires authorization):

import AppleHealth from "apple-health";

const dateOfBirth = await AppleHealth.getDateOfBirth();
const biologicalSex = await AppleHealth.getBiologicalSex();
const bloodType = await AppleHealth.getBloodType();
const skinType = await AppleHealth.getFitzpatrickSkinType();
const wheelchairUse = await AppleHealth.getWheelchairUse();

Deleting Samples

Delete samples you've written:

// Delete via sample object
await sample.delete();

// Delete via hook
const { deleteSample } = useHealthKitQuery({
  type: "stepCount",
  kind: "quantity",
});
await deleteSample(sample);

// Delete by date range
await AppleHealth.deleteSamples(
  "stepCount",
  startDate.toISOString(),
  endDate.toISOString()
);

Data Types

Quantity Types

| Category | Types | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Body | bodyMass, height, bodyFatPercentage, bodyMassIndex, leanBodyMass, waistCircumference | | Fitness | stepCount, distanceWalkingRunning, distanceCycling, activeEnergyBurned, basalEnergyBurned, flightsClimbed, appleExerciseTime, vo2Max | | Vitals | heartRate, restingHeartRate, walkingHeartRateAverage, heartRateVariabilitySDNN, bloodPressureSystolic, bloodPressureDiastolic, oxygenSaturation, respiratoryRate, bodyTemperature | | Nutrition | dietaryEnergyConsumed, dietaryProtein, dietaryCarbohydrates, dietaryFatTotal, dietaryCaffeine, dietaryWater |

Category Types

| Category | Types | | ------------------- | ---------------------------------------------------------------------------- | | Sleep | sleepAnalysis | | Activity | appleStandHour, lowCardioFitnessEvent | | Heart | highHeartRateEvent, lowHeartRateEvent, irregularHeartRhythmEvent | | Symptoms | headache, fatigue, fever, nausea, dizziness, shortnessOfBreath | | Mindfulness | mindfulSession | | Reproductive Health | menstrualFlow*, cervicalMucusQuality, ovulationTestResult, sexualActivity |

* menstrualFlow requires the HKMenstrualCycleStart metadata key (boolean). See Required Metadata.

Sleep Values

| Value | Meaning | | ----- | ------------------ | | 0 | In Bed | | 2 | Awake | | 3 | Core Sleep (light) | | 4 | Deep Sleep | | 5 | REM Sleep |

Symptom Values

| Value | Meaning | | ----- | ----------- | | 0 | Not Present | | 1 | Mild | | 2 | Moderate | | 3 | Severe |

Required Metadata

Some category types require specific metadata keys to be set. If metadata is missing, HealthKit will reject the sample with a validation error.

| Type | Required Metadata Key | Type | Description | | -------------- | ----------------------- | ------- | -------------------------------------------------- | | menstrualFlow | HKMenstrualCycleStart | boolean | true if this sample starts a new menstrual cycle |

Example:

await new HealthKitSampleBuilder()
  .categoryType("menstrualFlow")
  .categoryValue(2) // light flow
  .startDate(startOfDay)
  .endDate(endOfDay)
  .metadata({ HKMenstrualCycleStart: true }) // Required!
  .save();

TypeScript Types

import type {
  // Data type identifiers
  QuantityTypeIdentifier,
  CategoryTypeIdentifier,
  CharacteristicTypeIdentifier,
  WorkoutActivityType,
  HealthKitDataType,

  // Sample types
  QuantitySample,
  CategorySample,
  WorkoutSample,
  HealthKitSample,

  // Query/statistics
  StatisticsResult,
  StatisticsAggregation,
  ActivitySummary,

  // Authorization
  HealthKitPermissions,
  AuthorizationResult,
  AuthorizationStatus,

  // Permissions (Expo-style)
  HealthKitPermissionOptions,
  HealthKitPermissionResponse,
} from "apple-health";

// PermissionStatus enum
import { PermissionStatus } from "apple-health";
// PermissionStatus.GRANTED | PermissionStatus.DENIED | PermissionStatus.UNDETERMINED

CLI & DevTools

This package includes a CLI for querying and writing HealthKit data during development.

Quick Start

  1. Enable devtools in your app:

    import { useHealthKitDevTools } from "apple-health/dev-tools";
    
    export default function App() {
      useHealthKitDevTools();
      return <YourApp />;
    }
  2. Run CLI commands:

    # Check connection
    bunx apple-health status
    
    # Write data with natural date formats
    bunx apple-health write quantity heartRate 72 --start "today 8am"
    bunx apple-health write quantity stepCount 8000 --start "yesterday" --duration "1d"
    
    # Query data
    bunx apple-health query quantity heartRate --limit 10
    
    # Get statistics
    bunx apple-health stats stepCount --interval day --start "-7d"
    
    # Interactive mode
    bunx apple-health repl

Documentation


Notes

  • iOS only - HealthKit is not available on Android
  • Simulator limitations - The iOS Simulator has limited HealthKit data; use a real device for full testing
  • Privacy - HealthKit hides read authorization status; query data to verify access
  • iOS 16+ types - Some types (e.g., runningPower, underwaterDepth) require iOS 16+

Contributing

Contributions are very welcome! Please refer to guidelines described in the contributing guide.