Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save stoyanK7/77eeb61257de4d988d37f6e0466d071c to your computer and use it in GitHub Desktop.
Save stoyanK7/77eeb61257de4d988d37f6e0466d071c to your computer and use it in GitHub Desktop.

Import Apple Health Workout with Heart Rate data into Strava

Problem

I had a problem with importing a particular Apple Health workout into Strava. The workout was recorded on an Apple Watch SE and even though it showed up in Strava, upon clicking the Import button, the loading spinner appeared but the workout never imported. I assume it was because I had turned on Low Power Mode on my Apple Watch in the middle of the workout, which might have caused some issues with the data.

There are some free apps in the App Store that can export the Apple Health workout GPX file, but I found none that could export the heart rate data along with the workout.

There is an app called RunGap that can export the Apple Health workout with heart rate data, but it is a paid app and I didn't want to pay for it just for one workout.

So, how can I export an Apple Health workout with heart rate data for free and import it into Strava?

Solution

  1. Export Apple Health data

    1. Go to the Health app on your iPhone.
    2. Tap your profile picture in the upper-right corner.
    3. Tap Export All Health Data. This might take a while depending on how much data you have. I had to wait 5 minutes for it to finish.
    4. Select a location to save the exported file. It will be saved as a ZIP file containing XML files.
  2. Extract the ZIP file to a folder on your computer. The export folder for me was named apple_health_export/.

  3. The folder should have the following structure:

    apple_health_export/
    ├── export_cda.xml
    ├── export.xml
    └── workout-routes
        ├── route_2025-07-24_5.44pm.gpx
        ├── route_2025-07-22_7.01pm.gpx
        └── route_...gpx
    
  4. Find the GPX file in workout-routes/ for the workout you want to import into Strava. In my case, it was workout-routes/route_2025-07-24_5.44pm.gpx.

  5. Create a Python file named create_gpx_with_heart_rate.py and copy the following code into it:

    import xml.etree.ElementTree as ET
    from datetime import datetime, timedelta
    import pytz
    import os
    
    GPX_FILE = os.path.join("workout-routes", "YOUR_ROUTE.gpx")  # CHANGE THIS!
    EXPORT_XML = "export.xml"
    OUTPUT_FILE = "workout_with_hr.gpx"
    
    
    def main():
        print("Parsing heart rate data...")
        hr_records = parse_heart_rate_records(EXPORT_XML)
    
        print("Creating new GPX with heart rate data...")
        create_gpx_with_heart_rate_data(GPX_FILE, hr_records, OUTPUT_FILE)
    
    
    def parse_heart_rate_records(xml_path):
        tree = ET.parse(xml_path)
        root = tree.getroot()
    
        hr_data = []
    
        for record in root.findall("Record"):
            if record.get("type") == "HKQuantityTypeIdentifierHeartRate":
                # startData and endDate seem to be the same, doesn't matter which one we use
                time_str = record.get("startDate")
                value = float(record.get("value"))
                # Convert the heart rate record to UTC because the GPX timestamps are in UTC
                # and the heart rate records are in local time
                dt = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S %z").astimezone(
                    pytz.utc
                )
                hr_data.append((dt, value))
    
        return sorted(hr_data, key=lambda x: x[0])
    
    
    def create_gpx_with_heart_rate_data(gpx_path, hr_data, output_path):
        ET.register_namespace("", "http://www.topografix.com/GPX/1/1")
        ET.register_namespace(
            "gpxtpx", "http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
        )
    
        tree = ET.parse(gpx_path)
        root = tree.getroot()
    
        ns = {
            "default": "http://www.topografix.com/GPX/1/1",
        }
    
        for trkpt in root.findall(".//default:trkpt", ns):
            time_elem = trkpt.find("default:time", ns)
            if time_elem is None:
                continue
            time_str = time_elem.text
            try:
                pt_time = datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%SZ").replace(
                    tzinfo=pytz.utc
                )
            except ValueError:
                continue
    
            # Find nearest heart rate point (within 5 seconds for accuracy)
            nearest_hr = None
            min_diff = timedelta(seconds=5)
    
            for hr_time, hr_value in hr_data:
                diff = abs(hr_time - pt_time)
                if diff < min_diff:
                    min_diff = diff
                    nearest_hr = hr_value
    
            if nearest_hr:
                ext = trkpt.find("default:extensions", ns)
                if ext is None:
                    ext = ET.SubElement(
                        trkpt, "{http://www.topografix.com/GPX/1/1}extensions"
                    )
    
                tpx = ET.SubElement(
                    ext,
                    "{http://www.garmin.com/xmlschemas/TrackPointExtension/v1}TrackPointExtension",
                )
                hr = ET.SubElement(
                    tpx, "{http://www.garmin.com/xmlschemas/TrackPointExtension/v1}hr"
                )
                hr.text = str(int(nearest_hr))
    
        tree.write(output_path, encoding="utf-8", xml_declaration=True)
        print(f"Saved GPX with heart rate data to: {output_path}")
    
    
    if __name__ == "__main__":
        main()
  6. Replace YOUR_ROUTE.gpx (on line 6) in the code with the name of your GPX file (for example, route_2025-07-24_5.44pm.gpx).

  7. Create a virtual environment and install the required dependencies:

    python3 -m venv venv
    source venv/bin/activate
    pip install pytz
  8. Run the script:

    python3 create_gpx_with_heart_rate.py
  9. The script will create a new GPX file named workout_with_hr.gpx in the same directory as the script, which contains the heart rate data.

  10. Verify that the new GPX file has heart rate data by opening it in a text editor.

  11. Log in to your Strava account from the web browser.

  12. Go to the top right corner and click on the + icon, then select Upload Activity.

  13. On the left, click on File, then click on Choose Files and select the workout_with_hr.gpx file you just created.

  14. Enjoy your workout with heart rate data imported into Strava :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment