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?
-
- Go to the Health app on your iPhone.
- Tap your profile picture in the upper-right corner.
- 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. - Select a location to save the exported file. It will be saved as a ZIP file containing XML files.
-
Extract the ZIP file to a folder on your computer. The export folder for me was named
apple_health_export/
. -
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
-
Find the GPX file in
workout-routes/
for the workout you want to import into Strava. In my case, it wasworkout-routes/route_2025-07-24_5.44pm.gpx
. -
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()
-
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
). -
Create a virtual environment and install the required dependencies:
python3 -m venv venv source venv/bin/activate pip install pytz
-
Run the script:
python3 create_gpx_with_heart_rate.py
-
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. -
Verify that the new GPX file has heart rate data by opening it in a text editor.
-
Log in to your Strava account from the web browser.
-
Go to the top right corner and click on the
+
icon, then selectUpload Activity
. -
On the left, click on
File
, then click onChoose Files
and select theworkout_with_hr.gpx
file you just created. -
Enjoy your workout with heart rate data imported into Strava :)