Files
The Motivation
We live in the age of the "Quantified Self." For many of us, our health data, such as steps walked, floors climbed, and resting heart rate, is a diary of our physical lives. But when you switch ecosystems (e.g., moving from a Fitbit to an Apple Watch), that history is often left behind.
I recently switched to an Apple Watch Ultra 3 and refused to lose my more than 10 years of history (2015–2025). This tutorial shows how to retrieve our personal data from the Fitbit ecosystem (read: Google) and transform it into a format that can be loaded into Apple Health.
Why Not Use an App?
There are plenty of "Sync" apps on the App Store, but for a Data Engineer, they fall short:
- Throttling: Syncing over 3,800 days (11 years) via API often hits rate limits, taking days or weeks to finish.
- Data Loss: Most apps summarize data into daily totals, discarding the minute-by-minute granularity available in your archives.
- Missing Metrics: Advanced metrics like Resting Heart Rate and Flights Climbed are often ignored by simple transfer tools.
The FME Alternative
As FME users, we have a "Ferrari" in our garage. Why take the bus?
FME allows us to:
- Process locally (no API limits).
- Handle massive volumes (thousands of files) in seconds.
- Transform schema on the fly.
- Output exactly what we need for the next step.
Step-by-step Instructions
Part 1: The Source (Google Takeout)
1. Export Data with Google Takeout
You can use the Fitbit app to export your personal data via Google Takeout, but it is more convenient to go straight to the source on your computer. Navigate to https://takeout.google.com. Deselect All at the top (so you don't download your entire Gmail and YouTube history!). Scroll down to Fitbit. Check the box for Fitbit.
Click Next Step. Choose your destination, select Export once, your preferred archive format, and the file size. My full archive was about 360Mb, so any size should work. Click Create export. This will initiate the export process, and in a few hours (it took 5 hours in my case), you will receive an email with a link to a single ZIP file containing a folder structure with thousands of JSON and CSV files covering your entire account history. My archive consisted of over 24,000 files—a complete record of every single piece of information Fitbit ever logged about me.
Part 2: FME Form
In the tutorial, we will concentrate on three main metrics: daily step and floor counts, and Resting Heart Rate (RHR), also a daily stat.
The goal of this workspace is to aggregate hundreds of monthly files into clean Yearly JSON summaries.
-
Why Yearly? iOS Shortcuts runs on your phone/tablet. If you feed it 10MB of JSON, it may crash. Splitting data by year (e.g.,
fitbit_2016.json) is the perfect balance.
Fitbit stores all kinds of activity files in monthly CSVs in the Physical_Activity_GoogleData folder. For example, altitude, gps_location, sedentary_period, steps, floors, heart_rate etc.
Our output JSONs will have a very simple structure, an array of three-field records with the type of activity (t), date (d), and the actual value (v):
{
"data" : [
{
"t" : "steps",
"d" : "2025-07-10",
"v" : 17900
},
{
"t" : "floors",
"d" : "2025-07-10",
"v" : 160
},
{
"t" : "RHR",
"d" : "2025-07-10",
"v" : 59
},
{
…………………………………
}
]
}
Reading the Steps and Floors (CSVs)
We will begin by reading the step data. The step files have steps_YYYY-mm-dd name format (for example, steps_2024-11-01.csv).
1. Add CSV Reader
Add a reader to a blank workspace in FME Workbench.
- Format: CSV (Comma Separated Value)
-
Dataset: /Physical Activity_GoogleData/steps_*.csv
-
Use asterisk (*) to read all the files beginning with the
steps_
-
Use asterisk (*) to read all the files beginning with the
- Workflow Options: Single Merged Feature Type
Then click on Parameters.
In the CSV (Comma Separated Value) Parameters:
- Delimiter Character: Auto
- Field Names Line: 1
- Data Start Line: 2
-
Attributes:
- Attribute Definition: Manual
- data source: unchecked
Then click OK twice to finish adding the reader.
2. Format datetime
The files can contain thousands upon thousands of records (I had over 1.2 million of step lines) - Fitbit saves step count updates very frequently. We want to move only the daily step count to Apple Health; this is why we need to group our steps by date. For this, we use DateTimeConverter to format Fitbit datetime values (e.g. 2024-11-01T22:13:00Z) to date only (2024-11-01):
- Datetime Attributes: timestamp
- Input Format: %Y%m%d%H%M%S+00:00
- Output Format: %Y-%m-%d (ISO date)
- Repair Overflow: No
- Passthrough Nulls, Empties, or Missing: Yes
3. Aggregate Step Records to Daily Sums
Next, we will aggregate the step records to daily sums using an Aggregator.
-
Group Processing: Enabled
- Group By: timestamp:
- Aggregation Mode: Attributes Only
- Attributes to Sum: steps
4. Create Attributes
Add an AttributeCreator and create the following three attributes that we will use for JSON creation:
| Output Attribute | Value |
|---|---|
| type | steps |
| year | @Value(timestamp) |
| value | @Value(steps) |
For the type attribute, this is a constant value of "steps," whereas the other two attributes are the values of the attributes.
5. Repeat Steps for floors
Repeat steps 1-4 for floors. Use floors instead of steps in the CSV reader, Aggregator, and AttributeCreator.
Reading Heart Rate (JSON)
Do not read the granular "Heart Rate" files in Global Export Data folder (my dataset contained over 12,000,000 records). Look for resting_heart_rate-*.json.
1. Add a JSON Reader
In the same workspace, add a third reader.
- Format: JSON (JavaScript Object Notation)
-
Dataset: /Global Export Data/resting_heart_rate-*.json
-
Use an asterisk (*) to read all the files beginning with the
resting_heart_rate-
-
Use an asterisk (*) to read all the files beginning with the
-
Parameters:
- Flatten Nested JSON Values into Attributes: Yes
2. Convert datetime for Heart Rate
We need to create the same set of attributes for heart rate as we made for steps and floors. The dateTime attribute has a bit different format. Using the DateTimeConverter, we'll transform dates looking like %m/%d/%y2026 %H:%M:%S (for example, 12/31/20 00:00:00) to the same date-only format:
- Datetime Attributes: dateTime
- Input Format: %m/%d/%y2026 %H:%M:%S
- Output Format: %Y-%m-%d (ISO date)
- Repair Overflow: Yes
- Passthrough Nulls, Empties, or Missing: Yes
3. Round Heart Rate
The heart rate comes as a real64 number, we round it up to the closest integer. Add an AttributeRounder, in the parameters:
- Attributes to Round: value.value
- Decimal Places: 0
4. Create Attributes
| Output Attribute | Value |
|---|---|
| type | RHR |
| timestamp | @Value(dateTime) |
| value | @Value(value.value) |
| year | @Value(dateTime) |
5. Convert year
Now, in a new DateTimeConverter, we need to transform the year attribute containing the full date to years only. We will use this attribute for creating separate JSONs later.
- Datetime Attributes: year
- Input Format: %Y-%m-%d
- Output Format: %Y
6. Separate Data by Year
Our data is ready to be formatted as a JSON, but since we need separate files by year, we need a single feature each year to be a root node in the JSONTemplater. The easiest way to create unique features from a dataset is to use Sampler with Group By attribute. Here we will use the year attribute we just created:
-
Group Processing: Enabled
- Group By: year
- Sampling Rate (N): 1
- Sampling Type: First N Features
7. Add a JSONTemplater
Add a JSONTemplater. The single unique year features will go to the Root Template, and all features go into the DATA port we add as the Sub Template. Connect the Sampler Sampled port to the Root input port. In the parameters:
-
Group Processing: Enabled
- Group Sub Features By: year
-
Root Template:
- Port: ROOT
- Source: Expression
-
Template:
{ "data": [ fme:process-features("DATA") ] }
-
Sub Template: Enabled
- Click the plus sign to add a new sub template
- Port: DATA
- Source: Expression
-
Template:
{ "t": fme:get-attribute("type"), "d": fme:get-attribute("timestamp"), "v": fme:get-attribute("value") }
Now close the JSONTemplater and connect the DateTimeConverter_4 Output port to the newly created DATA port.
The Output (JSON Fanout)
1. Add a Text File Writer
Now we have features with aggregated daily statistics by year in the _result attribute. We will use a Text File writer, in which we set the text_line_data attribute to the value of the _result. Add a writer to the workspace:
- Format: Text File
- Dataset: /JSONfromFitbit
Then click OK. Connect the writer to the JSONTemplater.
2. Set Fanout Expression
In Navigator, expand JSONfromFitbit, then double-click on Fanout Expression.
In the Edit dialog:
- Fanout Dataset: Enabled
-
Fanout Expression:
fitbit_stats_@Value(_year).json
Here is the screenshot of the full workspace. If we run it, we get a series of json files with steps, floor, and resting heart rate data by year. In my case, 11 files, 11 years of data.
3. Copy the Files to your Device
Run the workspace. Then, using whatever method you chose, copy the JSON files to your iOS device. Now we are ready for the final part of the tutorial, ingesting this data into Apple Health.
Part 3: The iOS Bridge (Shortcuts)
Now for the "Capricious" part. We need an iOS Shortcut to read our clean JSON files and inject them directly into Apple Health.
The following section is a proof of concept only, and is not supported
by Safe Software Support. Any support beyond FME Workbench support is unavailable.
iOS Shortcuts are a powerful but sometimes unpredictable environment. The
workflow
below worked perfectly for my migration on iOS 26, but Apple frequently updates
how Shortcuts handles file permissions and variable types. This proof of
concept
to help you bridge the gap. If you encounter iOS-specific errors, we cannot
offer
troubleshooting support—the best resources will be the
Apple Shortcuts User Guide
or communities like r/shortcuts.
Handling Permissions
Writing to HealthKit requires explicit permission. You eventually need to go to Settings > Privacy > Health > Shortcuts and turn ON all write permissions (Steps, Flights Climbed, Heart Rate).
Don't panic if you check your Privacy settings right now and don't see "Steps" or "Heart Rate" listed. Apple "lazy loads" these toggles—they are hidden until the app actually asks for them.
How to activate permissions:
- Run the Shortcut for the first time.
- As soon as the script attempts to write the first record, iOS will interrupt you with a "Health Access" pop-up.
- Tap "Turn On All" (or manually select Steps, Flights Climbed, and Resting Heart Rate).
- Tap Allow.
Troubleshooting: If you accidentally tapped "Don't Allow" or need to fix it later, then you can go to Settings > Privacy & Security > Health > Shortcuts to toggle them back on. But you need to run the script once to generate those entries!
The Logic & The "Gotchas"
The logic is simple:
- Get File (e.g.,
fitbit_data_2016.json) - Parse JSON
- Loop through days
- Write to Health
However, there are two specific traps I fell into:
1. The "Magic Variable" (The Silent Failure)
This was the part that cost me the most time. In my early attempts, the script would run successfully, but zero data would show up in Apple Health.
- My Experience: When you add an action like "Get Dictionary Value," Shortcuts tries to be helpful by automatically linking it to the previous step. However, I found that these automatic links often passed the data as generic "Text" rather than the actual number or date object. When the script tried to write "Text" into a numeric Health field, it just gave up without an error message.
- The Fix: I stopped trusting the automatic connections entirely. I used the "Select Magic Variable" feature for every single step. By manually tapping the specific output bubble from the JSON parsing step, I forced Shortcuts to pass the correct data type.
2. The Date Parser
Do not use the generic "Date" action; it is brittle.
-
The Fix: Use the action "Get Dates from Input". It is a smart detector that correctly interprets
YYYY-MM-DDstrings.
The Shortcut
I’ve packaged this logic into a ready-to-use Shortcut. You can download it below:
Download the Fitbit JSON Importer
(Or scan the QR code below)
I had 11 JSON files (one per year), so I simply ran the shortcut 11 times. This brought every statistic I was interested in directly into Apple Health.
Final Cleanup: The "Double Data" Problem
Once I finished running the scripts, I noticed my step counts in Apple Health were almost double what they should be (e.g., 25,000 steps instead of 14,000).
This happens because the iPhone has its own internal pedometer. I rarely carry my phone in my pocket at home, so it recorded some steps of a day, which Apple Health was now adding to my imported Fitbit data.
The Fix (Prioritization):
- Open Health App > Steps (or Flights) > Data Sources & Access.
- Tap Edit.
- Drag Shortcuts to the top of the list.
- This tells Apple: "If you have data from Fitbit (Shortcuts) and the iPhone for the same minute, trust Fitbit."
The Alternative (The Nuclear Option):
If you want to be absolutely sure there is no overlap, tap the iPhone entry in that list, select Edit, and tap "Delete All". This removes the incomplete history recorded by your phone, leaving only your "Gold Standard" Fitbit migration.
And that’s it! After these steps, your data is safely ported to Apple Health, and you can officially retire your Fitbit.