Guides → export.xml is too big
Apple Health exports everything you have ever recorded into a single export.xml
file. For anyone who has worn a Watch for a year or two that file is routinely hundreds of megabytes, which
is why your text editor hangs, Numbers refuses it, and pasting it into ChatGPT or Claude is not an option.
You do not need to open the whole thing — you need to pull out the few records you actually want.
Published 24 July 2026
Because it is a complete, uncompressed event log rather than a summary. Every heart-rate reading your Watch has ever taken is its own XML element, and each one carries the full type identifier, source device, unit, and three timestamps. A single reading looks roughly like this:
<Record type="HKQuantityTypeIdentifierHeartRate" sourceName="Apple Watch"
sourceVersion="10.2" unit="count/min" creationDate="2026-07-24 09:14:02 +0100"
startDate="2026-07-24 09:14:00 +0100" endDate="2026-07-24 09:14:00 +0100"
value="62"/>
That is around 250 bytes of markup to store one number. The Watch samples heart rate continuously, so a single active day can produce thousands of these. Multiply by every metric and every day since you got the device and the arithmetic explains itself — the payload is small, the wrapper is not.
Three consequences follow, and they are the three things people actually search for:
Because of context limits, and the gap is not close. You can check the arithmetic for your own file rather than trusting a number from an article.
A widely used rule of thumb for English text and markup is roughly 4 characters per token. XML tends to be somewhat denser in tokens than prose because punctuation and attribute names split frequently, so treat this as a floor, not a precise figure. To get the real character count of your own export:
# macOS / Linux — character count of your export
wc -c apple_health_export/export.xml
# rough token floor
echo $(( $(wc -c < apple_health_export/export.xml) / 4 ))
On iPhone: open Health → tap your profile picture (top right) → Export All Health
Data → confirm. It takes several minutes and produces export.zip. Save it somewhere with
room — the unzipped folder is considerably larger than the archive.
Unzip it and you get apple_health_export/ containing export.xml, usually
export_cda.xml, and a workout-routes/ folder of GPX files.
Do not load the file into memory. Stream it, keep only the record types you care about, and throw the rest
away as you go. Python's iterparse does this in constant memory, so file size stops mattering:
import xml.etree.ElementTree as ET, csv
WANT = "HKQuantityTypeIdentifierHeartRateVariabilitySDNN"
with open("hrv.csv", "w", newline="") as out:
w = csv.writer(out)
w.writerow(["startDate", "value", "unit"])
for event, el in ET.iterparse("apple_health_export/export.xml", events=("end",)):
if el.tag == "Record" and el.get("type") == WANT:
w.writerow([el.get("startDate"), el.get("value"), el.get("unit")])
el.clear() # free the element immediately — this is the important line
The el.clear() call is what keeps memory flat. Without it you are back to holding the whole
document. Swap WANT for any identifier — HKQuantityTypeIdentifierStepCount,
HKQuantityTypeIdentifierRestingHeartRate, and so on. Our
reference of all 190 metrics lists the exact identifier for each.
To see which types your own file actually contains before you start:
grep -o 'type="HK[A-Za-z]*"' apple_health_export/export.xml | sort | uniq -c | sort -rn | head -30
Even one extracted metric is usually too granular. Thousands of heart-rate readings are not a useful input to a question like "how did my resting heart rate trend this year" — daily aggregates are. Aggregate as you stream:
import xml.etree.ElementTree as ET, json
from collections import defaultdict
daily = defaultdict(list)
WANT = "HKQuantityTypeIdentifierHeartRateVariabilitySDNN"
for event, el in ET.iterparse("apple_health_export/export.xml", events=("end",)):
if el.tag == "Record" and el.get("type") == WANT:
day = el.get("startDate")[:10] # YYYY-MM-DD
daily[day].append(float(el.get("value")))
el.clear()
out = [{"d": d, "v": round(sum(v) / len(v), 2)} for d, v in sorted(daily.items())]
json.dump(out, open("hrv-daily.json", "w"), indent=0)
print(len(out), "days")
This is the step that makes the data answerable. A year of daily values is a few hundred rows — small enough to hand to any model, and small enough to read yourself. Sum instead of average for cumulative metrics such as steps or active energy; average is right for rates such as heart rate and HRV.
The two fixes above are the honest answer if you want to stay in free tooling, and they work. The reason a dedicated tool exists is that this is a chore you have to repeat every time you want fresh data — Apple's export is a manual, all-or-nothing snapshot with no incremental option.
Health Export AI reads HealthKit directly on the phone and writes already-aggregated JSON, so there is no XML step at all. It also ships an open-source MCP server so an agent can query the result as live tools rather than being handed a file. The app is $2.99 once after a 7-day trial; the server is MIT-licensed and free.
| If you want to… | Use | Cost |
|---|---|---|
| Pull one metric out, once | Fix 1 — stream and filter | Free |
| Analyse trends, or feed a model | Fix 2 — daily aggregates | Free |
| Refresh regularly, or query from an agent | Fix 3 — export directly from the phone | Paid app, free server |
It depends entirely on how long you have been recording and whether you wear a Watch. Phone-only users with
step data measure in tens of megabytes; multi-year Watch users routinely see hundreds. Run
wc -c export.xml on your own file rather than relying on a typical figure — the variance between
two people is larger than any average is useful for.
No. It is a nested XML event log, not a table, so there is no sheet for it to become. Convert the records you want to CSV first — Fix 1 above does exactly that.
A Clinical Document Architecture version of a subset of your data, intended for exchange with healthcare
systems. It is not a smaller copy of export.xml and is usually not what you want for analysis.
No. The built-in export is XML only, all-or-nothing, and manual. Getting JSON or incremental updates means reading HealthKit through an app on the device.
That is your call, but it is worth being deliberate: the file contains your complete health history, not just the part relevant to your question. Extracting only the metric and date range you need — Fix 1 and 2 — is a meaningful reduction in what you share. Running the analysis locally shares nothing at all.