DEV Community

Robin for Capawesome

Posted on Originally published at capawesome.io

Announcing the Capacitor Health Plugin

Health data on mobile lives in two stores: Apple HealthKit on iOS and Health Connect on Android. If you build a fitness or health app with Capacitor, you normally end up writing two integrations that disagree on permissions, sleep modeling, and even what a "workout" is. We just released the Capacitor Health plugin to solve exactly that: one strictly typed API that reads, writes, and aggregates data from both stores.

Why a Health Plugin Now

The timing follows the platforms. The Google Fit APIs shut down at the end of 2026, and Health Connect takes over as the health store on Android, while HealthKit has held that role on iOS all along. Any cross-platform health integration you build today should target these two stores — and ideally without maintaining two data models in your app code.

Aggregation First

Most health plugins hand you raw record lists and leave the math to you. That breaks the moment a user wears a smartwatch: the watch and the phone both record steps for the same minutes, and summing the records in JavaScript counts that overlap twice.

Both HealthKit and Health Connect solve this with native aggregation queries that deduplicate sources before returning a number, so the plugin is built around them. A week of daily step totals is one call:

import { DataType, Health } from '@capawesome-team/capacitor-health';

const readDailySteps = async () => {
  const { buckets } = await Health.aggregate({
    dataType: DataType.Steps,
    startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
    endDate: new Date().toISOString(),
    bucket: 'day',
    operations: ['sum'],
  });
  return buckets.map((bucket) => bucket.values[0].value);
};
Enter fullscreen mode Exit fullscreen mode

The day, week, and month buckets are calendar-aware and follow the device's time zone. Cumulative data types such as steps, distance, and calories support sum; sampled types such as heart rate and weight support average, maximum, and minimum. An unsupported combination rejects with the INVALID_AGGREGATION error code instead of resolving with silently empty results, so a mistyped query fails in development instead of shipping as a dashboard full of zeros.

Availability and Permissions

Health Connect exists in three states on Android: available, not installed, or unsupported by the device. On Android 9 to 13 it's a separate app the user may not have; on Android 14 and later it's part of the operating system:

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

const checkAvailability = async () => {
  const { available, reason } = await Health.isAvailable();
  if (!available && reason === 'health-connect-not-installed') {
    await Health.installHealthConnect();
  }
  return available;
};
Enter fullscreen mode Exit fullscreen mode

Permissions are requested per data type and separately for reading and writing:

import { DataType, Health } from '@capawesome-team/capacitor-health';

const requestPermissions = async () => {
  const { permissions } = await Health.requestPermissions({
    read: [DataType.Steps, DataType.HeartRate, DataType.Sleep],
    write: [DataType.Weight],
  });
  return permissions;
};
Enter fullscreen mode Exit fullscreen mode

An Honest Permission Model

Here's a HealthKit detail that surprises most developers: iOS deliberately hides whether a read permission was granted. A denied permission that is distinguishable from missing data would leak sensitive information — an app that knows it was denied blood glucose access could conclude the user is likely diabetic.

The plugin reports iOS read permissions as prompt before the first request and unknown afterwards, never as granted. Reporting granted would be an invented value, so the plugin doesn't do it. Design your app around the presence of data: request the permissions, query, and show a helpful empty state when nothing comes back.

Reading and Writing Records

When you need individual samples instead of aggregates, readRecords() returns them with timestamps and source, and writeRecord() logs the record types apps commonly write:

import { DataType, Health } from '@capawesome-team/capacitor-health';

const readHeartRateSamples = async () => {
  const { records } = await Health.readRecords({
    dataType: DataType.HeartRate,
    startDate: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
    endDate: new Date().toISOString(),
  });
  return records;
};

const logWeight = async () => {
  await Health.writeRecord({
    dataType: DataType.Weight,
    startDate: new Date().toISOString(),
    value: 71.5,
  });
};
Enter fullscreen mode Exit fullscreen mode

Workouts have their own reader: readWorkouts() returns exercise sessions with type, duration, and totals, whether logged by your app or another one.

Passing App Review

Health integrations fail review more often than they fail at runtime. Every Android app integrating with Health Connect must complete the Health apps declaration in the Google Play Console and provide a privacy policy; Apple reviews health apps against App Review Guideline 5.1.3. The plugin documentation includes dedicated sections for both, so the policy work is part of the setup instead of a surprise at submission time.

Availability

The Capacitor Health plugin covers around 20 data types, requires Capacitor 8 or later, and is available today as part of the Capawesome Insiders subscription. The full announcement with more details is on our blog: Announcing the Capacitor Health Plugin.

Questions or feedback? Drop a comment — happy to answer.

Top comments (0)