Skip to content

Cap-go/capacitor-health

@capgo/capacitor-health

Capgo - Instant updates for capacitor

Capacitor plugin to read and write health metrics via Apple HealthKit (iOS) and Health Connect (Android). The TypeScript API keeps the same data types and units across platforms so you can build once and deploy everywhere.

Why Capacitor Health?

The only free, unified health data plugin for Capacitor supporting the latest native APIs:

  • Health Connect (Android) - Uses Google's newest health platform (replaces deprecated Google Fit)
  • HealthKit (iOS) - Full integration with Apple's health framework
  • Unified API - Same TypeScript interface across platforms with consistent units
  • Multiple metrics - Steps, distance, calories, heart rate, weight
  • Read & Write - Query historical data and save new health entries
  • Modern standards - Supports Android 8.0+ and iOS 14+
  • Modern package management - Supports both Swift Package Manager (SPM) and CocoaPods (SPM-ready for Capacitor 8)

Perfect for fitness apps, health trackers, wellness platforms, and medical applications.

Documentation

The most complete doc is available here: https://capgo.app/docs/plugins/health/

Compatibility

Plugin version Capacitor compatibility Maintained
v8.*.* v8.*.*
v7.*.* v7.*.* On demand
v6.*.* v6.*.*
v5.*.* v5.*.*

Note: The major version of this plugin follows the major version of Capacitor. Use the version that matches your Capacitor installation (e.g., plugin v8 for Capacitor 8). Only the latest major version is actively maintained.

Install

npm install @capgo/capacitor-health
npx cap sync

iOS Setup

  1. Open your Capacitor application's Xcode workspace and enable the HealthKit capability.
  2. Provide usage descriptions in Info.plist (update the copy for your product):
<key>NSHealthShareUsageDescription</key>
<string>This app reads your health data to personalise your experience.</string>
<key>NSHealthUpdateUsageDescription</key>
<string>This app writes new health entries that you explicitly create.</string>

Android Setup

This plugin now uses Health Connect instead of Google Fit. Make sure your app meets the requirements below:

  1. Min SDK 26+. Health Connect is only available on Android 8.0 (API 26) and above. The plugin's Gradle setup already targets this level.
  2. Declare Health permissions. The plugin manifest ships with the required <uses-permission> declarations for basic data types (READ_/WRITE_STEPS, READ_/WRITE_DISTANCE, READ_/WRITE_ACTIVE_CALORIES_BURNED, READ_/WRITE_HEART_RATE, READ_/WRITE_WEIGHT, READ_/WRITE_SLEEP, READ_/WRITE_RESPIRATORY_RATE, READ_/WRITE_OXYGEN_SATURATION, READ_/WRITE_RESTING_HEART_RATE, READ_/WRITE_HEART_RATE_VARIABILITY). Your app does not need to duplicate them, but you must surface a user-facing rationale because the permissions are considered health sensitive.
  3. Ensure Health Connect is installed. Devices on Android 14+ include it by default. For earlier versions the user must install Health Connect by Android from the Play Store. The Health.isAvailable() helper exposes the current status so you can prompt accordingly.
  4. Request runtime access. The plugin opens the Health Connect permission UI when you call requestAuthorization. You should still handle denial flows (e.g., show a message if checkAuthorization reports missing scopes).
  5. Provide a Privacy Policy. Health Connect requires apps to display a privacy policy explaining how health data is used. See the Privacy Policy Setup section below.

If you already used Google Fit in your project you can remove the associated dependencies (play-services-fitness, play-services-auth, OAuth configuration, etc.).

Privacy Policy Setup

Health Connect requires your app to provide a privacy policy that explains how you handle health data. When users tap "Privacy policy" in the Health Connect permissions dialog, your app must display this information.

Option 1: HTML file in assets (recommended for simple cases)

Place an HTML file at android/app/src/main/assets/public/privacypolicy.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Privacy Policy</title>
  </head>
  <body>
    <h1>Privacy Policy</h1>
    <p>Your privacy policy content here...</p>
    <h2>Health Data</h2>
    <p>Explain how you collect, use, and protect health data...</p>
  </body>
</html>

Option 2: Custom URL (recommended for hosted privacy policies)

Add a string resource to your app's android/app/src/main/res/values/strings.xml:

<resources>
    <!-- Your other strings... -->
    <string name="health_connect_privacy_policy_url">https://yourapp.com/privacy-policy</string>
</resources>

This URL will be loaded in a WebView when the user requests to see your privacy policy.

Programmatic access:

You can also show the privacy policy or open Health Connect settings from your app:

// Show the privacy policy screen
await Health.showPrivacyPolicy();

// Open Health Connect settings (useful for managing permissions)
await Health.openHealthConnectSettings();

Usage

import { Health } from '@capgo/capacitor-health';

// Verify that the native health SDK is present on this device
const availability = await Health.isAvailable();
if (!availability.available) {
  console.warn('Health access unavailable:', availability.reason);
}

// Ask for separate read/write access scopes
await Health.requestAuthorization({
  read: ['steps', 'heartRate', 'weight'],
  write: ['weight'],
});

// Query the last 50 step samples from the past 24 hours
const { samples } = await Health.readSamples({
  dataType: 'steps',
  startDate: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
  endDate: new Date().toISOString(),
  limit: 50,
});

// Persist a new body-weight entry (kilograms by default)
await Health.saveSample({
  dataType: 'weight',
  value: 74.3,
});

Supported data types

Identifier Default unit Notes
steps count Step count deltas
distance meter Walking / running distance
calories kilocalorie Active energy burned
heartRate bpm Beats per minute
weight kilogram Body mass
sleep minute Sleep sessions with duration and states
respiratoryRate bpm Breaths per minute
oxygenSaturation percent Blood oxygen saturation (SpO2)
restingHeartRate bpm Resting heart rate
heartRateVariability millisecond Heart rate variability (HRV)
bloodPressure mmHg Blood pressure (requires systolic/diastolic values)
bloodGlucose mg/dL Blood glucose level
bodyTemperature celsius Body temperature
height centimeter Body height
flightsClimbed count Floors / flights of stairs climbed
exerciseTime minute Apple Exercise Time (iOS only)
distanceCycling meter Cycling distance
bodyFat percent Body fat percentage
basalBodyTemperature celsius Basal body temperature
basalCalories kilocalorie Basal metabolic rate / resting energy
totalCalories kilocalorie Total energy burned (active + basal)
mindfulness minute Mindfulness / meditation sessions
workouts N/A Workout sessions (read-only, use with queryWorkouts())

All write operations expect the default unit shown above. On Android the metadata option is currently ignored by Health Connect.

Blood Pressure: Blood pressure requires both systolic and diastolic values:

await Health.saveSample({
  dataType: 'bloodPressure',
  value: 120, // systolic value (used as main value)
  systolic: 120,
  diastolic: 80,
  startDate: new Date().toISOString(),
});

// Reading blood pressure returns samples with systolic/diastolic fields
const { samples } = await Health.readSamples({
  dataType: 'bloodPressure',
  startDate: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
  endDate: new Date().toISOString(),
});

samples.forEach((sample) => {
  console.log(`BP: ${sample.systolic}/${sample.diastolic} mmHg`);
});

Note about workouts: To query workout data using queryWorkouts(), you need to explicitly request workouts permission:

await Health.requestAuthorization({
  read: ['steps', 'workouts'], // Include 'workouts' to access workout sessions
});

Pagination example: Use the anchor parameter to paginate through workout results:

// First page: get the first 10 workouts
let result = await Health.queryWorkouts({
  startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(), // Last 30 days
  endDate: new Date().toISOString(),
  limit: 10,
});

console.log(`Found ${result.workouts.length} workouts`);

// If there are more results, the anchor will be set
while (result.anchor) {
  // Next page: use the anchor to continue from where we left off
  result = await Health.queryWorkouts({
    startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
    endDate: new Date().toISOString(),
    limit: 10,
    anchor: result.anchor, // Continue from the last result
  });

  console.log(`Found ${result.workouts.length} more workouts`);
}

New Data Types Examples

Sleep data:

// Request permission for sleep data
await Health.requestAuthorization({
  read: ['sleep'],
});

// Read sleep sessions from the past week
const { samples } = await Health.readSamples({
  dataType: 'sleep',
  startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
  endDate: new Date().toISOString(),
});

samples.forEach(sample => {
  console.log(`Sleep: ${sample.value} minutes, state: ${sample.sleepState}`);
});

Respiratory rate, oxygen saturation, and HRV:

// Request permission
await Health.requestAuthorization({
  read: ['respiratoryRate', 'oxygenSaturation', 'restingHeartRate', 'heartRateVariability'],
});

// Read respiratory rate
const { samples: respiratoryRate } = await Health.readSamples({
  dataType: 'respiratoryRate',
  startDate: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
  endDate: new Date().toISOString(),
});

// Read oxygen saturation (SpO2)
const { samples: oxygenSat } = await Health.readSamples({
  dataType: 'oxygenSaturation',
  startDate: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
  endDate: new Date().toISOString(),
});

Aggregated Queries

For large date ranges, use queryAggregated() to get aggregated data efficiently instead of fetching individual samples:

// Get daily step totals for the past month
const { samples } = await Health.queryAggregated({
  dataType: 'steps',
  startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
  endDate: new Date().toISOString(),
  bucket: 'day',        // Options: 'hour', 'day', 'week', 'month'
  aggregation: 'sum',   // Options: 'sum', 'average', 'min', 'max'
});

samples.forEach(sample => {
  console.log(`${sample.startDate}: ${sample.value} ${sample.unit}`);
});

// Get average heart rate by day
const { samples: avgHR } = await Health.queryAggregated({
  dataType: 'heartRate',
  startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
  endDate: new Date().toISOString(),
  bucket: 'day',
  aggregation: 'average',
});

Note: Aggregated queries are not supported for sleep, respiratory rate, oxygen saturation, and heart rate variability data types. These are instantaneous measurements and should use readSamples() instead. Aggregation is supported for: steps, distance, calories, heart rate, weight, and resting heart rate.

API

isAvailable()

isAvailable() => Promise<AvailabilityResult>

Returns whether the current platform supports the native health SDK.

Returns: Promise<AvailabilityResult>


requestAuthorization(...)

requestAuthorization(options: AuthorizationOptions) => Promise<AuthorizationStatus>

Requests read/write access to the provided data types.

Param Type
options AuthorizationOptions

Returns: Promise<AuthorizationStatus>


checkAuthorization(...)

checkAuthorization(options: AuthorizationOptions) => Promise<AuthorizationStatus>

Checks authorization status for the provided data types without prompting the user.

Param Type
options AuthorizationOptions

Returns: Promise<AuthorizationStatus>


readSamples(...)

readSamples(options: QueryOptions) => Promise<ReadSamplesResult>

Reads samples for the given data type within the specified time frame.

Param Type
options QueryOptions

Returns: Promise<ReadSamplesResult>


saveSample(...)

saveSample(options: WriteSampleOptions) => Promise<void>

Writes a single sample to the native health store.

Param Type
options WriteSampleOptions

getPluginVersion()

getPluginVersion() => Promise<{ version: string; }>

Get the native Capacitor plugin version

Returns: Promise<{ version: string; }>


openHealthConnectSettings()

openHealthConnectSettings() => Promise<void>

Opens the Health Connect settings screen (Android only). On iOS, this method does nothing.

Use this to direct users to manage their Health Connect permissions or to install Health Connect if not available.


showPrivacyPolicy()

showPrivacyPolicy() => Promise<void>

Shows the app's privacy policy for Health Connect (Android only). On iOS, this method does nothing.

This displays the same privacy policy screen that Health Connect shows when the user taps "Privacy policy" in the permissions dialog.

The privacy policy URL can be configured by adding a string resource named "health_connect_privacy_policy_url" in your app's strings.xml, or by placing an HTML file at www/privacypolicy.html in your assets.


queryWorkouts(...)

queryWorkouts(options: QueryWorkoutsOptions) => Promise<QueryWorkoutsResult>

Queries workout sessions from the native health store. Supported on iOS (HealthKit) and Android (Health Connect).

Param Type Description
options QueryWorkoutsOptions Query options including optional workout type filter, date range, limit, and sort order

Returns: Promise<QueryWorkoutsResult>


queryAggregated(...)

queryAggregated(options: QueryAggregatedOptions) => Promise<QueryAggregatedResult>

Queries aggregated health data from the native health store. Aggregates data into time buckets (hour, day, week, month) with operations like sum, average, min, or max. This is more efficient than fetching individual samples for large date ranges.

Supported on iOS (HealthKit) and Android (Health Connect).

Param Type Description
options QueryAggregatedOptions Query options including data type, date range, bucket size, and aggregation type

Returns: Promise<QueryAggregatedResult>


Interfaces

AvailabilityResult

Prop Type Description
available boolean
platform 'ios' | 'android' | 'web' Platform specific details (for debugging/diagnostics).
reason string

AuthorizationStatus

Prop Type
readAuthorized HealthDataType[]
readDenied HealthDataType[]
writeAuthorized HealthDataType[]
writeDenied HealthDataType[]

AuthorizationOptions

Prop Type Description
read HealthDataType[] Data types that should be readable after authorization.
write HealthDataType[] Data types that should be writable after authorization.

ReadSamplesResult

Prop Type
samples HealthSample[]

HealthSample

Prop Type Description
dataType HealthDataType
value number
unit HealthUnit
startDate string
endDate string
sourceName string
sourceId string
platformId string Platform-specific unique identifier (HealthKit UUID on iOS, Health Connect metadata ID on Android).
sleepState SleepState For sleep data, indicates the sleep state (e.g., 'asleep', 'awake', 'rem', 'deep', 'light').
systolic number For blood pressure data, the systolic value in mmHg.
diastolic number For blood pressure data, the diastolic value in mmHg.

QueryOptions

Prop Type Description
dataType HealthDataType The type of data to retrieve from the health store.
startDate string Inclusive ISO 8601 start date (defaults to now - 1 day).
endDate string Exclusive ISO 8601 end date (defaults to now).
limit number Maximum number of samples to return (defaults to 100).
ascending boolean Return results sorted ascending by start date (defaults to false).

WriteSampleOptions

Prop Type Description
dataType HealthDataType
value number
unit HealthUnit Optional unit override. If omitted, the default unit for the data type is used (count for steps, meter for distance, kilocalorie for calories, bpm for heartRate, kilogram for weight).
startDate string ISO 8601 start date for the sample. Defaults to now.
endDate string ISO 8601 end date for the sample. Defaults to startDate.
metadata Record<string, string> Metadata key-value pairs forwarded to the native APIs where supported.
systolic number For blood pressure data, the systolic value in mmHg. Required when dataType is 'bloodPressure'.
diastolic number For blood pressure data, the diastolic value in mmHg. Required when dataType is 'bloodPressure'.

QueryWorkoutsResult

Prop Type Description
workouts Workout[]
anchor string Anchor for the next page of results. Pass this value as the anchor parameter in the next query to continue pagination. If undefined or null, there are no more results.

Workout

Prop Type Description
workoutType WorkoutType The type of workout.
duration number Duration of the workout in seconds.
totalEnergyBurned number Total energy burned in kilocalories (if available).
totalDistance number Total distance in meters (if available).
startDate string ISO 8601 start date of the workout.
endDate string ISO 8601 end date of the workout.
sourceName string Source name that recorded the workout.
sourceId string Source bundle identifier.
platformId string Platform-specific unique identifier (HealthKit UUID on iOS, Health Connect metadata ID on Android).
metadata Record<string, string> Additional metadata (if available).

QueryWorkoutsOptions

Prop Type Description
workoutType WorkoutType Optional workout type filter. If omitted, all workout types are returned.
startDate string Inclusive ISO 8601 start date (defaults to now - 1 day).
endDate string Exclusive ISO 8601 end date (defaults to now).
limit number Maximum number of workouts to return (defaults to 100).
ascending boolean Return results sorted ascending by start date (defaults to false).
anchor string Anchor for pagination. Use the anchor returned from a previous query to continue from that point. On iOS, this is the ISO 8601 cursor returned by the previous query. On Android, this uses Health Connect's pageToken. Omit this parameter to start from the beginning.

QueryAggregatedResult

Prop Type
samples AggregatedSample[]

AggregatedSample

Prop Type Description
startDate string ISO 8601 start date of the bucket.
endDate string ISO 8601 end date of the bucket.
value number Aggregated value for the bucket.
unit HealthUnit Unit of the aggregated value.

QueryAggregatedOptions

Prop Type Description
dataType HealthDataType The type of data to aggregate from the health store.
startDate string Inclusive ISO 8601 start date (defaults to now - 1 day).
endDate string Exclusive ISO 8601 end date (defaults to now).
bucket BucketType Time bucket for aggregation (defaults to 'day').
aggregation AggregationType Aggregation operation to perform (defaults to 'sum').

Type Aliases

HealthDataType

'steps' | 'distance' | 'calories' | 'heartRate' | 'weight' | 'sleep' | 'respiratoryRate' | 'oxygenSaturation' | 'restingHeartRate' | 'heartRateVariability' | 'bloodPressure' | 'bloodGlucose' | 'bodyTemperature' | 'height' | 'flightsClimbed' | 'exerciseTime' | 'distanceCycling' | 'bodyFat' | 'basalBodyTemperature' | 'basalCalories' | 'totalCalories' | 'mindfulness' | 'workouts'

HealthUnit

'count' | 'meter' | 'kilocalorie' | 'bpm' | 'kilogram' | 'minute' | 'percent' | 'millisecond' | 'mmHg' | 'mg/dL' | 'celsius' | 'fahrenheit' | 'centimeter'

SleepState

'inBed' | 'asleep' | 'awake' | 'rem' | 'deep' | 'light'

Record

Construct a type with a set of properties K of type T

{ [P in K]: T; }

WorkoutType

'americanFootball' | 'australianFootball' | 'badminton' | 'baseball' | 'basketball' | 'bowling' | 'boxing' | 'climbing' | 'cricket' | 'crossTraining' | 'curling' | 'cycling' | 'dance' | 'elliptical' | 'fencing' | 'functionalStrengthTraining' | 'golf' | 'gymnastics' | 'handball' | 'hiking' | 'hockey' | 'jumpRope' | 'kickboxing' | 'lacrosse' | 'martialArts' | 'pilates' | 'racquetball' | 'rowing' | 'rugby' | 'running' | 'sailing' | 'skatingSports' | 'skiing' | 'snowboarding' | 'soccer' | 'softball' | 'squash' | 'stairClimbing' | 'strengthTraining' | 'surfing' | 'swimming' | 'swimmingPool' | 'swimmingOpenWater' | 'tableTennis' | 'tennis' | 'trackAndField' | 'traditionalStrengthTraining' | 'volleyball' | 'walking' | 'waterFitness' | 'waterPolo' | 'waterSports' | 'weightlifting' | 'wheelchair' | 'yoga' | 'archery' | 'barre' | 'cooldown' | 'coreTraining' | 'crossCountrySkiing' | 'discSports' | 'downhillSkiing' | 'equestrianSports' | 'fishing' | 'fitnessGaming' | 'flexibility' | 'handCycling' | 'highIntensityIntervalTraining' | 'hunting' | 'mindAndBody' | 'mixedCardio' | 'paddleSports' | 'pickleball' | 'play' | 'preparationAndRecovery' | 'snowSports' | 'stairs' | 'stepTraining' | 'surfingSports' | 'taiChi' | 'transition' | 'underwaterDiving' | 'wheelchairRunPace' | 'wheelchairWalkPace' | 'wrestling' | 'cardioDance' | 'socialDance' | 'backExtension' | 'barbellShoulderPress' | 'benchPress' | 'benchSitUp' | 'bikingStationary' | 'bootCamp' | 'burpee' | 'calisthenics' | 'crunch' | 'dancing' | 'deadlift' | 'dumbbellCurlLeftArm' | 'dumbbellCurlRightArm' | 'dumbbellFrontRaise' | 'dumbbellLateralRaise' | 'dumbbellTricepsExtensionLeftArm' | 'dumbbellTricepsExtensionRightArm' | 'dumbbellTricepsExtensionTwoArm' | 'exerciseClass' | 'forwardTwist' | 'frisbeedisc' | 'guidedBreathing' | 'iceHockey' | 'iceSkating' | 'jumpingJack' | 'latPullDown' | 'lunge' | 'meditation' | 'paddling' | 'paraGliding' | 'plank' | 'rockClimbing' | 'rollerHockey' | 'rowingMachine' | 'runningTreadmill' | 'scubaDiving' | 'skating' | 'snowshoeing' | 'stairClimbingMachine' | 'stretching' | 'upperTwist' | 'other'

BucketType

'hour' | 'day' | 'week' | 'month'

AggregationType

'sum' | 'average' | 'min' | 'max'

Credits:

this plugin was inspired by the work of https://github.com/perfood/capacitor-healthkit/ for ios and https://github.com/perfood/capacitor-google-fit for Android

About

Capacitor plugin to interact with data from Apple healthKit and Google Fit

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Contributors