Library / SDK
kingstinct/react-native-healthkit avatar
kingstinct/react-native-healthkit

react-native-healthkit and the TypeScript mapping of Apple's health data

HealthKit bindings for React Native with TypeScript

721 stars90 forksTypeScriptMIT

At a glance

What is it?
react-native-healthkit is a React Native binding for HealthKit with TypeScript types kept deliberately close to Apple's own naming, covering a hundred quantity types, sixty-three category types and more than seventy-five workout types. The friction is not the API surface but the authorisation order, since reading data before asking for permission crashes the app.
Who is it for?
react-native-healthkit is a good fit for a React Native health app that wants typed access to most of HealthKit without hand-writing a native module, because the type mapping stays close to Apple's identifiers and both hooks and imperative calls are available. It is a poor fit for a cross-platform app that expects HealthKit data on Android, and for any project that cannot use a dev client build, since the README is explicit that Expo Go will not work.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A data coverage table that tells you where the gaps are

The library's value is easiest to see in its coverage table, which lists the data families in HealthKit against four capabilities: query, save, subscribe and examples.

There are 100+ quantity types with full support, covering things like steps, energy burnt and blood glucose. There are 63 category types, also fully supported, with sleep analysis and mindful sessions as the examples. There are 75+ workout activity types, from swimming and running to table tennis, again with all four capabilities. Correlation types, such as food and blood pressure, are supported across the board too.

The two gaps are the interesting part. Document types, which cover CDA documents, can be queried and subscribed to but not saved, and they come back as Base64 data. Clinical records, covering lab results and immunizations, can also be queried and subscribed to but not saved, and they are not returned as HealthKit samples at all: they come as FHIR through a separate package, @react-native-healthkit/health-records.

That last point is a design decision rather than a missing feature. HealthKit's clinical record API is shaped differently from the sample APIs, and mapping it onto the same shape would have been lossy, so the project ships it separately.

Keeping TypeScript names close to Apple's identifiers

The design principle stated in the README is that the TypeScript mappings stay as close as possible to HealthKit, both in naming and in serialization, so that the library is easier to keep up to date with HealthKit and easier to browse against Apple's official documentation.

That principle shows in the API. The identifiers you pass are Apple's constants, verbatim: HKQuantityTypeIdentifierBloodGlucose, HKCategoryTypeIdentifierMindfulSession, HKQuantityTypeIdentifierBodyFatPercentage, HKQuantityTypeIdentifierHeartRate. You are not learning a second vocabulary.

The README also makes a point about what happens when a type is not covered. If something is not typed, for example a metadata property, it is still accessible. So the type layer is a convenience over the full data rather than a gate that hides the parts it does not model.

The package is MIT licensed, and the disclaimer is unambiguous: the library is provided as-is without any warranty and is not affiliated with Apple in any way, and the data might be incomplete or inaccurate. That last sentence is the one to keep in mind when you build a product on it, since HealthKit data on a user device is only ever as good as what the user typed.

Authorisation first, or the app crashes

The single most important operational note in the README is at the top of the usage section: during runtime, check and request permissions with requestAuthorization, and failing to request authorisation, or requesting a permission you have not requested yet, will result in the app crashing.

The README calls this easy to miss and gives a concrete way to hit it: requesting authorisation in the same component where a hook is already trying to fetch data.

The hook API is designed around that constraint. A hook named useHealthkitAuthorization takes the type identifiers you want and returns the current status along with a request function:

ts
import { useHealthkitAuthorization, saveQuantitySample } from '@kingstinct/react-native-healthkit';
const [authorizationStatus, requestAuthorization] = useHealthkitAuthorization(['HKQuantityTypeIdentifierBloodGlucose'])

Only once that has run are the data hooks safe to mount. The README's example shows the pairing directly:

ts
import { useMostRecentQuantitySample, HKQuantityTypeIdentifier, useMostRecentCategorySample } from '@kingstinct/react-native-healthkit';
const mostRecentBloodGlucoseSample = useMostRecentQuantitySample('HKQuantityTypeIdentifierBloodGlucose')
const lastBodyFatSample = useMostRecentQuantitySample('HKQuantityTypeIdentifierBodyFatPercentage')
const lastMindfulSession = useMostRecentCategorySample('HKCategoryTypeIdentifierMindfulSession')
const lastWorkout = useMostRecentWorkout()

The imperative API follows the same ordering discipline, and the README is explicit that permissions must be requested before subscribing to changes.

The imperative path: read a sample, then subscribe

Everything the hooks do is also available as plain async functions, which is what you want outside a component or in a background task.

The sequence in the README starts with an availability check, then authorisation for the specific types you intend to read, then the read itself. A sample read returns the pieces you would want:

ts
  const isAvailable = await isHealthDataAvailable();
  await requestAuthorization({ toRead: ['HKQuantityTypeIdentifierBodyFatPercentage'] }); // request read permission for bodyFatPercentage
  const { quantity, unit, startDate, endDate } = await getMostRecentQuantitySample('HKQuantityTypeIdentifierBodyFatPercentage'); // read latest sample
  console.log(quantity) // 17.5
  console.log(unit) // %

Note that requestAuthorization takes an object with a toRead array here, while the hook example earlier passes the array directly. That inconsistency is in the project's own examples rather than something to smooth over, so check the signature you are calling.

Subscription is the third step and returns an unsubscribe function, which is what you return from the effect's cleanup:

ts
      const unsubscribe = subscribeToChanges('HKQuantityTypeIdentifierHeartRate', () => {
        // refetch data as needed
      });
      return () => unsubscribe();

The callback is a signal to refetch rather than a data payload, so your code decides what to pull when a change arrives.

Two packages, and why the core one is pinned exactly

The install story involves three packages, and the reason is a detail of the React Native CLI.

@react-native-healthkit/core holds the code shared with @react-native-healthkit/health-records: the types, the config plugin building blocks and the ReactNativeHealthkitCore pod. It is a regular dependency pinned to an exact version, so the package manager installs the matching copy for you and there is no version to keep in sync.

In a bare React Native app you additionally need it in your own package.json, because the React Native CLI only autolinks packages listed there. The native or Expo bare workflow therefore installs all three:

The third package, react-native-nitro-modules, is the Nitro engine the native side is built on, and it is not optional in that command.

After the install come the native steps: npx pod-install, then set NSHealthUpdateUsageDescription and NSHealthShareUsageDescription in the Info.plist, then enable the HealthKit capability for the project in Xcode. Because the package is written in Swift, you may also need a bridging header, and the README links the React Native documentation on exporting Swift.

That list is the real cost of a HealthKit feature: two plist strings, a capability toggle, a pod install and possibly a bridging header, none of which the JavaScript can do for you.

Expo needs a dev client, and the config plugin sets the defaults

Expo is supported with one firm restriction. Usage with Expo is possible, but it will not work in Expo Go, and you need to roll your own Dev Client.

The config plugin is what makes the build work without further configuration. The install step is `yarn add @kingstinct/react-native-healthkit react-native-nitro-modules`, and the minimal app.json entry is:

json
{
  "expo": {
    "plugins": ["@kingstinct/react-native-healthkit"]
  }
}

The defaults can be overridden, which is where you supply your own usage description strings and turn on background delivery:

json
{
  "expo": {
    "plugins": [
      ["@kingstinct/react-native-healthkit", {
        "NSHealthShareUsageDescription": "Your own custom usage description",
        "NSHealthUpdateUsageDescription": "Your own custom usage description",
        "background": true
      }]
    ]
  }
}

The two usage description strings are what iOS shows in the permission prompt, and they are the only place your product's words appear to the user before HealthKit access. The background option is the one to think about, because it asks for delivery of updates while the app is not running, which has different battery and review implications from foreground-only access.

After changing app.json you build a new Dev Client, and the install step itself is a single command.

The monorepo layout in the repository explains how much machinery sits behind these three packages. There is a packages directory, an apps directory with an example app, a scripts directory, a docs directory, and a .changeset directory for release versioning. The package.json scripts show the pipeline: codegen variants that rebuild the nitrogen generated sources, a build:core step, a fix-codegen step that rewrites generated Swift files with perl, swiftlint for linting, and biome for the JavaScript side. The build tooling is Bun based, with a bun.lock at the root.

Maintenance, versioning and the clinical records split

The repository is not archived and the last push was on 2026-10-12. Three packages were released the same day at version 16.2.0: @react-native-healthkit/health-records, @react-native-healthkit/core and @kingstinct/react-native-healthkit. Releasing all three together and pinning core to an exact version is a deliberate strategy: it means the shared types and the pod always match the package that depends on them.

The licence is MIT, which places no conditions on commercial use of the bindings themselves. Separately, the disclaimer in the README says the library is not affiliated with Apple in any way, so you cannot present it as an Apple product, and the data might be incomplete or inaccurate.

The clinical records path is worth its own look. Rather than pretending that lab results and immunizations are ordinary samples, the project routes them through @react-native-healthkit/health-records and returns them as FHIR, the HL7 FHIR JSON format. That means a team building on clinical records is also building on a data model that a hospital or EHR system would recognise, which is a real advantage if the goal is to hand records to another clinical system rather than just display them.

The repository also carries agent tooling: AGENTS.md, CLAUDE.md, a .claude directory, a skills directory with a skills-lock.json, and a .agents directory. For a library maintained with AI-assisted tooling in the open, that is a signal about how the work gets done, and it is visible rather than hidden.

Editorial conclusion

react-native-healthkit is a good fit for a React Native health app that wants typed access to most of HealthKit without hand-writing a native module, because the type mapping stays close to Apple's identifiers and both hooks and imperative calls are available. It is a poor fit for a cross-platform app that expects HealthKit data on Android, and for any project that cannot use a dev client build, since the README is explicit that Expo Go will not work. Before integrating, call isHealthDataAvailable and request authorisation before any hook reads data, add both usage description strings and enable the HealthKit capability in Xcode, and decide how clinical records will be handled, since they arrive as FHIR through a separate package rather than as ordinary samples.

Frequently asked questions

How do I install react-native-healthkit in a bare React Native app?

Run yarn add @kingstinct/react-native-healthkit @react-native-healthkit/core react-native-nitro-modules, then npx pod-install, set NSHealthUpdateUsageDescription and NSHealthShareUsageDescription in your Info.plist, and enable the HealthKit capability in Xcode. The core package must be listed explicitly because the React Native CLI only autolinks packages in your own package.json.

Can I use react-native-healthkit with Expo Go?

No. The README states that usage with Expo is possible but will not work in Expo Go, and that you need to roll your own Dev Client. The config plugin entry in app.json is what supplies the defaults that make the app build.

Why does my app crash when it reads HealthKit data?

Failing to request authorization, or reading before requesting it, crashes the app. The README suggests this is easy to miss, for example by requesting authorization in the same component where a hook tries to fetch data right away, so authorize first with requestAuthorization or useHealthkitAuthorization.

How does react-native-healthkit return clinical records?

Clinical records such as lab results and immunizations are queryable and subscribable but not savable, and they are delivered as FHIR through the separate @react-native-healthkit/health-records package rather than as ordinary HealthKit samples.

Official sources

  1. kingstinct/react-native-healthkit on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/kingstinct-react-native-healthkit.svg)](https://hysenlabs.com/projects/kingstinct-react-native-healthkit)

Was this useful?