Read health data

Check authorization, read raw samples, and read totals for a fixed set of health data types.

Check availability

Call status() to see whether Health is available on this device and the per-type authorization state before you read anything. Health can be switched off in a given app build — check bdk.capabilities.has("health.read") before showing any health UI.

When it isn't available, status() returns a reason:

ReasonMeaning
device_unsupportedThis device has no health data provider.
provider_missingThe OS health app / provider isn't installed.
provider_update_requiredThe provider is installed but needs an update.

Outside the app the command doesn't run: the returned object has no ok field and triggered: false. status()'s result is a union — check "ok" in result before reading envelope-only fields.

import { createBdkNative } from "@bdk/native/browser";

const bdk = createBdkNative();

if (await bdk.capabilities.has("health.read")) {
  const result = await bdk.health.status();

  if ("ok" in result && result.ok) {
    console.log(result.available, result.reason, result.types);
    // types: { steps: { status: "granted", requested: true }, ... }
  }
}

Request access

Ask for authorization to read health data. Pass types to request a subset, or omit it to request every type your app build enables.

On iOS, read authorization always reports unknown — HealthKit never reveals whether a read was granted or denied. A denied read looks exactly like an empty result, so treat an empty read as possibly-denied, not as "no data recorded".

const result = await bdk.health.request({});

if ("ok" in result && result.ok) {
  console.log(result.types);
} else if ("ok" in result) {
  console.warn(result.code, result.message);
} else if (!result.triggered) {
  return;
}

Read totals

Use aggregate() for steps, distance, and active calories instead of summing raw samples yourself — it buckets and sums on the native side so you don't double-count across sources.

interval controls the bucket size: total, hour, day, or month. The result carries unit (the type's unit), stat (sum for quantity totals, avg for types like heart rate), and the buckets themselves.

const result = await bdk.health.aggregate({ type: "steps", interval: "day" });

if ("ok" in result && result.ok) {
  console.log(result.unit, result.stat); // "count" "sum"
  for (const bucket of result.buckets) {
    console.log(bucket);
  }
} else if ("ok" in result) {
  console.warn(result.code, result.message);
} else if (!result.triggered) {
  return;
}

Read raw samples

Read individual samples for a type over a date range when a total isn't enough — for example, plotting each heart-rate reading.

limit caps how many samples come back; check truncated to see if there are more than limit in range, and use startDate/endDate/ascending to page through them.

const result = await bdk.health.read({
  type: "heartRate",
  startDate: "2026-08-01T00:00:00Z",
  endDate: "2026-08-08T00:00:00Z",
  limit: 200,
  ascending: true
});

if ("ok" in result && result.ok) {
  console.log(result.kind, result.unit, result.count, result.truncated);
  console.log(result.samples);
} else if ("ok" in result) {
  console.warn(result.code, result.message);
} else if (!result.triggered) {
  return;
}

List enabled types

enabledTypes() returns the HealthTypeIds this app build enables — an empty array when Health is off. Use it to build a picker instead of hardcoding the full list.

HealthTypeIdKindUnit / aggregate
stepsquantitycount / sum
distancequantitym / sum
activeCaloriesquantitykcal / sum
heartRatequantitybpm / discrete
restingHeartRatequantitybpm / discrete
weightquantitykg / discrete
heightquantitycm / discrete
bloodOxygenquantity% / discrete
sleepsessionstages
workoutssessionactivities

aggregate() doesn't support sleep or workouts — they're session types, not summable quantities. Use read() to pull their raw samples instead.

const types = await bdk.health.enabledTypes();
console.log(types); // e.g. ["steps", "distance", "heartRate"]