FIT, GPX or TCX: what survives a workout export?
Keep your original recording before converting it. For a cycling workout with power or cadence, the original FIT is a good starting point when your destination accepts it. TCX can be useful when you need XML. GPX can carry a route and sensor data, but the receiving app has to understand its extensions.
We exported one small, synthetic ride in all three formats and read the files back. Each kept the route, heart rate, cadence and power. The differences appeared in the totals—and in what an importer had to calculate for itself.
What survived in these files
The test ride has six points over five minutes. Heart rate, cadence and power each have one deliberately missing value. Cadence and power also include a recorded zero. These results describe the corrected WorkoutPort source code linked below and this sample; we did not test an App Store build or a device.
| Data in the file | FIT | GPX | TCX |
|---|---|---|---|
| Route and timestamps | 6 points | 6 points | 6 points |
| Heart rate, cadence and power | 5 samples each | 5 samples each | 5 samples each |
| Recorded speed | 6 samples | 6 samples | 6 samples |
| Cumulative distance | 6 samples | Absent | 6 samples |
| Explicit duration / distance / calories | 300 s / 1,350 m / 42 kcal | Absent | 300 s / 1,350 m / 42 kcal |
The GPX sensor values live in extensions: extra fields alongside the basic route. Removing those extensions from a copy left all six route points and no sensor samples. That was a deliberate control to show where the data lives; we did not observe a converter or service discarding it.
“GPX has no heart rate” is therefore too broad. The GPX standard allows extensions, and Strava documents the sensor extensions it reads. Check the particular exporter and destination you use.
What happened when we read them back into WorkoutPort
We also ran the actual WorkoutPort file parser on these exports. It read six route points and five power samples from each original file. It kept the zero-power value and the missing value separate.
| Previewed value | FIT | GPX | TCX |
|---|---|---|---|
| Duration | 300 s | 300 s | 300 s |
| Distance | 1,350 m | About 1,334.34 m | 1,350 m |
| Calories | 42 kcal | Unavailable | 42 kcal |
Why the different GPX distance? This exporter supplies no distance total in GPX, so the importer calculates one from the coordinates. We deliberately gave the synthetic ride a supplied distance that differs from its invented route length, making a preserved total easy to distinguish from a calculated one. The difference is a test condition, not a measurement of GPS accuracy.
The useful lesson is that a familiar-looking map can come with a different distance or a missing calorie total. Check the destination’s values as well as the file’s contents.
A missing sample and zero mean different things
At minute two, our sample records zero watts. At minute three, it records no power value. Replacing that gap with zero would invent a measurement and could change an average.
Inspect a known gap or stop in the sensor chart. Check that recorded values and missing values both survived the transfer.
Check one activity before transferring more
- Save the original. Record its start time, distance and the sensor streams you expect. Strava’s Export Original, for example, returns the format originally uploaded.
- Use a format the destination accepts. Start with the original FIT when available and supported. If you need another format, keep the original alongside it.
- Inspect the imported activity. Compare the start time, elapsed time, distance, route and important sensor streams. Check calories if you use them. Moving time may be recalculated by the destination.
If power is present in the file but absent at the destination, keep the file and investigate the importer. If it is already absent from the file, go back to the source export. This separates two different problems before you attempt the whole history.
Files, method and limits
Download the FIT, GPX, TCX and GPX control. The decoded samples and local import results show the values behind the tables.
All data is synthetic: six sparse points, invented coordinates and separately supplied distance and speed. We used the actual FIT builder and the app’s GPX/TCX serializer methods with small test-only model adapters. Independent readback used fitdecode 0.11.0 with CRC checks and a namespace-aware XML parser. The local import check used WorkoutPort’s own parser; it is a separate stage, not an independent compatibility test.
The corrected GPX passes the GPX 1.1 schema with Garmin’s trackpoint and power schemas loaded. TCX passes Garmin’s Training Center Database v2 schema with its activity-extension schema loaded. Schema validity does not prove that a receiving app supports those extensions or will accept the files. FIT elevation encoding changed these test values by up to 0.08 m. Pauses, laps, swimming, strength sets, device recording and service uploads are outside this test.
Correction, 12 October: the first GPX download put power and speed in the wrong XML namespace. We corrected the native serializer in source, regenerated the sample and added schema validation before republishing. The TCX schema check also exposed an incomplete optional Creator element, which we removed.
The instructions, inputs, independent reader, local preview runner, schema results, tested source and checksums make the method inspectable. For a whole archive, follow the small-batch migration checklist before moving the rest.